Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Установить фокус на TextBox в WPF из модели представления

Автор вопроса: priyanka.sarkarОтветов: 12

На мой взгляд, у меня есть TextBox и Button.

Теперь я проверяю условие при нажатии кнопки, и если условие оказывается ложным, отображаю сообщение пользователю, а затем мне нужно установить курсор на элемент управления TextBox.

if (companyref == null)
{
    var cs = new Lipper.Nelson.AdminClient.Main.Views.ContactPanels.CompanyAssociation();

    MessageBox.Show("Company does not exist.", "Error", MessageBoxButton.OK,
                    MessageBoxImage.Exclamation);

    cs.txtCompanyID.Focusable = true;

    System.Windows.Input.Keyboard.Focus(cs.txtCompanyID);
}

Приведенный выше код находится в ViewModel.

CompanyAssociation - это имя представления.

Но курсор не устанавливается в TextBox.

XAML:

<igEditors:XamTextEditor Name="txtCompanyID"
                         KeyDown="xamTextEditorAllowOnlyNumeric_KeyDown"
                         ValueChanged="txtCompanyID_ValueChanged"
                         Text="{Binding Company.CompanyId,
                                        Mode=TwoWay,
                                        UpdateSourceTrigger=PropertyChanged}"
                         Width="{Binding ActualWidth, ElementName=border}"
                         Grid.Column="1" Grid.Row="0"
                         VerticalAlignment="Top"
                         HorizontalAlignment="Stretch"
                         Margin="0,5,0,0"
                         IsEnabled="{Binding Path=IsEditable}"/>

<Button Template="{StaticResource buttonTemp1}"
        Command="{Binding ContactCommand}"
        CommandParameter="searchCompany"
        Content="Search"
        Width="80"
        Grid.Row="0" Grid.Column="2"
        VerticalAlignment="Top"
        Margin="0"
        HorizontalAlignment="Left"
        IsEnabled="{Binding Path=IsEditable}"/>
  • Когда вы используете caliburn.micro, это отличное решение. matze8426 31.07.2018

mvvmwpfxamltextbox

Ответы

Ответ 1 · рейтинг 280 · Anvaka

Разрешите мне ответить на ваш вопрос в трех частях.

  1. Мне интересно, что такое "cs.txtCompanyID" в вашем примере? Это элемент управления TextBox? Если да, то вы ошиблись. Вообще говоря, не рекомендуется иметь какую-либо ссылку на пользовательский интерфейс в вашей ViewModel. Вы можете спросить "Почему?" но это еще один вопрос для публикации в Stackoverflow :).

  2. Лучший способ отследить проблемы с Focus - это ... отладить исходный код .Net. Без шуток. Это много раз экономило мне много времени. Чтобы включить отладку исходного кода .net, обратитесь к Блог Шона Брука.

  3. Наконец, общий подход, который я использую для установки фокуса из ViewModel, - это прикрепленные свойства. Я написал очень простое вложенное свойство, которое можно установить на любом UIElement. И его можно связать, например, со свойством ViewModel "IsFocused". Вот:

    public static class FocusExtension
    {
        public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool) obj.GetValue(IsFocusedProperty);
        }
    
        public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }
    
        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.RegisterAttached(
                "IsFocused", typeof (bool), typeof (FocusExtension),
                new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
    
        private static void OnIsFocusedPropertyChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var uie = (UIElement) d;
            if ((bool) e.NewValue)
            {
                uie.Focus(); // Don't care about false values.
            }
        }
    }
    

    Теперь в вашем представлении (в XAML) вы можете привязать это свойство к вашей ViewModel:

    <TextBox local:FocusExtension.IsFocused="{Binding IsUserNameFocused}" />
    

Надеюсь это поможет :). Если это не относится к ответу №2.

Ваше здоровье.

Ответ 2 · рейтинг 82 · Zamotic

Я знаю, что на этот вопрос уже тысячи раз ответили, но я внес некоторые правки в статью Анваки, которая, я думаю, поможет другим, у которых были похожие проблемы, которые были у меня.

Во-первых, я изменил указанное выше Attached Property следующим образом:

public static class FocusExtension
{
    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?), typeof(FocusExtension), new FrameworkPropertyMetadata(IsFocusedChanged){BindsTwoWayByDefault = true});

    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        return (bool?)element.GetValue(IsFocusedProperty);
    }

    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        element.SetValue(IsFocusedProperty, value);
    }

    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)d;

        if (e.OldValue == null)
        {
            fe.GotFocus += FrameworkElement_GotFocus;
            fe.LostFocus += FrameworkElement_LostFocus;
        }

        if (!fe.IsVisible)
        {
            fe.IsVisibleChanged += new DependencyPropertyChangedEventHandler(fe_IsVisibleChanged);
        }

        if (e.NewValue != null && (bool)e.NewValue)
        {
            fe.Focus();
        }
    }

    private static void fe_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)sender;
        if (fe.IsVisible && (bool)fe.GetValue(IsFocusedProperty))
        {
            fe.IsVisibleChanged -= fe_IsVisibleChanged;
            fe.Focus();
        }
    }

    private static void FrameworkElement_GotFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, true);
    }

    private static void FrameworkElement_LostFocus(object sender, RoutedEventArgs e)
    {
        ((FrameworkElement)sender).SetValue(IsFocusedProperty, false);
    }
}

Причиной добавления ссылок на видимость были вкладки. Очевидно, если вы использовали присоединенное свойство на любой другой вкладке за пределами изначально видимой вкладки, присоединенное свойство не работало, пока вы вручную не сфокусировали элемент управления.

Другим препятствием было создание более элегантного способа сброса базового свойства на false при потере фокуса. Вот тут-то и появились события потерянного фокуса.

<TextBox
    Text="{Binding Description}"
    FocusExtension.IsFocused="{Binding IsFocused}"/>

Если есть лучший способ решить проблему с видимостью, дайте мне знать.

Примечание. Спасибо Apfelkuacha за предложение поместить BindsTwoWayByDefault в DependencyProperty. Я сделал это давно в своем собственном коде, но никогда не обновлял этот пост. Из-за этого изменения Mode = TwoWay больше не требуется в коде WPF.

Ответ 3 · рейтинг 32 · Adam

Я думаю, что лучший способ - сохранить принцип MVVM в чистоте, поэтому в основном вы должны использовать класс Messenger, предоставляемый с MVVM Light, и вот как его использовать:

в вашей модели просмотра (exampleViewModel.cs): напишите следующее

 Messenger.Default.Send<string>("focus", "DoFocus");

теперь в вашем View.cs (а не в XAML view.xaml.cs) напишите следующее в конструкторе

 public MyView()
        {
            InitializeComponent();

            Messenger.Default.Register<string>(this, "DoFocus", doFocus);
        }
        public void doFocus(string msg)
        {
            if (msg == "focus")
                this.txtcode.Focus();
        }

этот метод работает нормально, с меньшим количеством кода и поддержкой стандартов MVVM

Ответ 4 · рейтинг 17 · Leo Vildosola

Ни один из них не сработал для меня точно, но для других я написал именно это на основе некоторого кода, уже предоставленного здесь.

Использование будет следующим:

<TextBox ... h:FocusBehavior.IsFocused="True"/>

А реализация была бы такой:

/// <summary>
/// Behavior allowing to put focus on element from the view model in a MVVM implementation.
/// </summary>
public static class FocusBehavior
{
    #region Dependency Properties
    /// <summary>
    /// <c>IsFocused</c> dependency property.
    /// </summary>
    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached("IsFocused", typeof(bool?),
            typeof(FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
    /// <summary>
    /// Gets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <returns>Value of the <c>IsFocused</c> property or <c>null</c> if not set.</returns>
    public static bool? GetIsFocused(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        return (bool?)element.GetValue(IsFocusedProperty);
    }
    /// <summary>
    /// Sets the <c>IsFocused</c> property value.
    /// </summary>
    /// <param name="element">The element.</param>
    /// <param name="value">The value.</param>
    public static void SetIsFocused(DependencyObject element, bool? value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        element.SetValue(IsFocusedProperty, value);
    }
    #endregion Dependency Properties

    #region Event Handlers
    /// <summary>
    /// Determines whether the value of the dependency property <c>IsFocused</c> has change.
    /// </summary>
    /// <param name="d">The dependency object.</param>
    /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = d as FrameworkElement;
        if (fe != null && e.OldValue == null && e.NewValue != null && (bool)e.NewValue)
        {
            // Attach to the Loaded event to set the focus there. If we do it here it will
            // be overridden by the view rendering the framework element.
            fe.Loaded += FrameworkElementLoaded;
        }
    }
    /// <summary>
    /// Sets the focus when the framework element is loaded and ready to receive input.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
    private static void FrameworkElementLoaded(object sender, RoutedEventArgs e)
    {
        // Ensure it is a FrameworkElement instance.
        var fe = sender as FrameworkElement;
        if (fe != null)
        {
            // Remove the event handler registration.
            fe.Loaded -= FrameworkElementLoaded;
            // Set the focus to the given framework element.
            fe.Focus();
            // Determine if it is a text box like element.
            var tb = fe as TextBoxBase;
            if (tb != null)
            {
                // Select all text to be ready for replacement.
                tb.SelectAll();
            }
        }
    }
    #endregion Event Handlers
}

Ответ 5 · рейтинг 14 · Rich N

Это старый поток, но, похоже, нет ответа с кодом, который решает проблемы с принятым ответом Anavanka: он не работает, если вы установите для свойства в модели просмотра значение false или если вы установите для своего свойства значение true, пользователь вручную щелкает что-то еще, а затем вы снова устанавливаете для него значение true. Я также не мог заставить решение Zamotic работать надежно в этих случаях.

Собрав вместе некоторые из приведенных выше обсуждений, я получаю приведенный ниже код, который, как мне кажется, решает эти проблемы:

public static class FocusExtension
{
    public static bool GetIsFocused(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsFocusedProperty);
    }

    public static void SetIsFocused(DependencyObject obj, bool value)
    {
        obj.SetValue(IsFocusedProperty, value);
    }

    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached(
         "IsFocused", typeof(bool), typeof(FocusExtension),
         new UIPropertyMetadata(false, null, OnCoerceValue));

    private static object OnCoerceValue(DependencyObject d, object baseValue)
    {
        if ((bool)baseValue)
            ((UIElement)d).Focus();
        else if (((UIElement) d).IsFocused)
            Keyboard.ClearFocus();
        return ((bool)baseValue);
    }
}

Тем не менее, это все еще сложно для чего-то, что можно сделать одной строкой в ​​выделенном коде, и CoerceValue на самом деле не предназначен для использования таким образом, поэтому, возможно, выделенный код - это выход.

Ответ 6 · рейтинг 4 · Community

В моем случае FocusExtension не работал, пока я не изменил метод OnIsFocusedPropertyChanged. Исходный работал только при отладке, когда точка останова останавливала процесс. Во время выполнения процесс идет слишком быстро и ничего не происходит. Благодаря этой небольшой модификации и помощи нашего друга Task, это отлично работает в обоих сценариях.

private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  var uie = (UIElement)d;
  if ((bool)e.NewValue)
  {
    var action = new Action(() => uie.Dispatcher.BeginInvoke((Action)(() => uie.Focus())));
    Task.Factory.StartNew(action);
  }
}

Ответ 7 · рейтинг 3 · Shawn

Проблема в том, что если для IsUserNameFocused установлено значение true, оно никогда не будет ложным. Это решает проблему, обрабатывая GotFocus и LostFocus для FrameworkElement.

У меня возникли проблемы с форматированием исходного кода, поэтому вот ссылка

Ответ 8 · рейтинг 3 · PEK

Великолепный код Anvakas предназначен для настольных приложений Windows. Если вы похожи на меня и вам нужно такое же решение для приложений Магазина Windows, этот код может вам пригодиться:

public static class FocusExtension
{
    public static bool GetIsFocused(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsFocusedProperty);
    }


    public static void SetIsFocused(DependencyObject obj, bool value)
    {
        obj.SetValue(IsFocusedProperty, value);
    }


    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached(
         "IsFocused", typeof(bool), typeof(FocusExtension),
         new PropertyMetadata(false, OnIsFocusedPropertyChanged));


    private static void OnIsFocusedPropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue)
        {
            var uie = d as Windows.UI.Xaml.Controls.Control;

            if( uie != null )
            {
                uie.Focus(FocusState.Programmatic);
            }
        }
    }
}

Ответ 9 · рейтинг 1 · Kyeotic

Для тех, кто пытался использовать решение Anvaka выше, у меня были проблемы с привязкой, работающей только в первый раз, поскольку lostfocus не обновлял свойство до false. Вы можете вручную установить для свойства значение false, а затем true каждый раз, но лучшим решением могло бы быть выполнение чего-то вроде этого в своем свойстве:

bool _isFocused = false;
    public bool IsFocused
    {
        get { return _isFocused ; }
        set
        {
            _isFocused = false;
            _isFocused = value;
            base.OnPropertyChanged("IsFocused ");
        }
    }

Таким образом, вам нужно только установить для него значение true, и он получит фокус.

Ответ 10 · рейтинг 1 · kpp

Я использую WPF / Caliburn Micro и обнаружил, что dfaivre представил здесь общее и работоспособное решение: http://caliburnmicro.codeplex.com/discussions/222892

Ответ 11 · рейтинг 1 · Bharat Mendapara

Я нашел решение, отредактировав следующий код. Нет необходимости устанавливать свойство привязки сначала False, затем True.

public static class FocusExtension
{

    public static bool GetIsFocused(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsFocusedProperty);
    }


    public static void SetIsFocused(DependencyObject obj, bool value)
    {
        obj.SetValue(IsFocusedProperty, value);
    }


    public static readonly DependencyProperty IsFocusedProperty =
        DependencyProperty.RegisterAttached(
         "IsFocused", typeof(bool), typeof(FocusExtension),
         new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));


    private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d != null && d is Control)
        {
            var _Control = d as Control;
            if ((bool)e.NewValue)
            {
                // To set false value to get focus on control. if we don't set value to False then we have to set all binding
                //property to first False then True to set focus on control.
                OnLostFocus(_Control, null);
                _Control.Focus(); // Don't care about false values.
            }
        }
    }

    private static void OnLostFocus(object sender, RoutedEventArgs e)
    {
        if (sender != null && sender is Control)
        {
            (sender as Control).SetValue(IsFocusedProperty, false);
        }
    }
}

Ответ 12 · рейтинг 1 · stuicidle

Альтернативный подход, основанный на ответе @Sheridan, здесь

 <TextBox Text="{Binding SomeText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <TextBox.Style>
            <Style>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding SomeTextIsFocused, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
                        <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>

В вашей модели представления настройте привязку обычным способом, а затем установите SomeTextIsFocused на true, чтобы установить фокус на текстовом поле.

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы