@sergeyfk

Как обратиться к контролу из VM?

Как можно обратится по имени к контролу из VM?
Я пытаюсь сделать вызов команды при инициализации контрола и передать команде контрол, но для этого, как я понял нужно указать необходимые CommandParamers, но какие?
Или есть более изящное решение, codeBehind не приветствуется.
<intr:Interaction.Triggers>
                    <intr:EventTrigger EventName="Initialized">
                        <intr:InvokeCommandAction Command="{Binding command_InitializedTextBox}"/>
                    </intr:EventTrigger>
                </intr:Interaction.Triggers>


Примечание: В связи с тем, что в текстбоксе находится большой текст и часто обновляется. Через простой биндинг поля Text текстбокс долго его обновляет. Хотел бы попробовать воспользоваться решение типа такого:
textbox.Append("string");
  • Вопрос задан
  • 469 просмотров
Решения вопроса 1
@sergeyfk Автор вопроса
Я все таки написал свой костыль, который работает вполне не плохо, но не идеально. Сейчас я все вкратце расскажу...
Начнем со вспомоготельного класса для вызова комманд:
public class ActionCommand : ICommand
    {
        private Action _action;
        private Action<object> _actionObj;
        public ActionCommand(Action action)
        {
            _action = action;
        }
        public ActionCommand(Action<object> actionObj)
        {
            _actionObj = actionObj;
        }
        public bool CanExecute(object parameter)
        {
            return true;
        }
        public void Execute(object parameter)
        {
            if (parameter == null)
            {
                _action?.Invoke();
            }
            else if (parameter is System.Windows.Controls.TextBox)
            {
                _actionObj?.Invoke(parameter);
            }
        }
        public event EventHandler CanExecuteChanged;
    }

Думаю тут ничего не стоит объяснять. Описаний подобных классов хватает. Я лишь добавил Action с параметром и проверку параметра на null и тип необходимого мне объекта.

Идем дальше...
Нужно к тэгу Window дописать следущий параметр и добавить соответствующую библиотеку, которая есть в nuget
xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"


Далее создадим наш контрол:
<TextBox Text="{Binding Log, Mode=OneWay}" IsReadOnly="True" Margin="5" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                <intr:Interaction.Triggers>
                    <intr:EventTrigger EventName="TextChanged">
                        <intr:InvokeCommandAction Command="{Binding command_InitializedTextBox}" 
                                                  CommandParameter="{Binding RelativeSource=
                                                 {RelativeSource
                                                 Mode=FindAncestor,
                                                 AncestorType=TextBox}}"/>
                    </intr:EventTrigger>
                </intr:Interaction.Triggers>
            </TextBox>

Обращаю внимание на AncestorType=TextBox. Вам необходимо поменять значение на необходимое вам(как и во вспомогательном классе), а иначе передастся найденный TextBox, если он вообще есть. Отвечаю сразу на ожидаемые вопросы:
использовать событие Loaded и Initialized не получится в данном случае, т.к. VM объявляется позже, чем срабатывают эти события.

Ну и идем к скоплению костылей... Во ViewModel
public ICommand command_InitializedTextBox
        {
            get
            {
                return new ActionCommand((object sender) => { InitializedTextBox(sender); });
            }
        }
        private void InitializedTextBox(object sender)
        {
            _LogControl = ((System.Windows.Controls.TextBox)sender);
        }

Тут должно быть все понятно.

А вот void для добавления текста.
private void AppendLogText(string getter)
        {
            if (_LogControl != null)
            {
                _LogControl.AppendText(getter);
            }
            else
            {
                Log += getter;
            }
        }

Текст изменяется биндингом только 1 раз.

Хочу отметить, что использовать данное решение стоит когда используются большие текста. ТекстБокс долго "думает" при использовании больших текстов и биндинга, а при использовании AppendText - нет.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
@Samael7777
Если кому еще актуально... Столкнулся с аналогичной проблемой. События окна Loaded, Initialized, ContextChanged не перехватываются. Нормально ловится событие ContentRendered. Можно забиндиться на него, и не нужно выводить строчку текста. Нужно дать имя TextBox'у :
<TextBox x:Name="LogBox" ...
Код для окна:
<i:Interaction.Triggers>
            <i:EventTrigger EventName="ContentRendered">
            <i:InvokeCommandAction Command="{Binding SetLogBoxCmd}" CommandParameter="{Binding ElementName=LogBox}"/>
        </i:EventTrigger>
</i:Interaction.Triggers>


В ВМ параметр команды привести к TextBox, и можно юзать AppendText (обязательно в контексте текущего потока):
Application.Current.Dispatcher.Invoke(() => _logBox?.AppendText(message));

Имхо, это все равно костыль...
CodeBehind для автоматической прокрутки TextBox
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (!(sender is TextBox textBox)) return;

            textBox.CaretIndex = textBox.Text.Length;
            textBox.ScrollToEnd();
        }


Полное описание TextBox в xaml
<TextBox x:Name="LogBox" TextChanged="TextBox_OnTextChanged" VerticalScrollBarVisibility="Auto" Text="{Binding LogText, Mode=OneWay}" AcceptsReturn="True" VerticalAlignment="Top" IsReadOnly="True" AutoWordSelection="True" TextWrapping="Wrap"/>


В принципе, биндинг на Text уже не нужен.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы