[C#] 뷰 모델에서 WPF의 TextBox에 포커스 설정

나는이 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}"/>



답변

세 부분으로 귀하의 질문에 대답하겠습니다.

  1. 귀하의 예에서 “cs.txtCompanyID”가 무엇인지 궁금합니다. TextBox 컨트롤입니까? 그렇다면, 당신은 잘못된 길을 가고 있습니다. 일반적으로 ViewModel에서 UI에 대한 참조를 갖는 것은 좋지 않습니다. “왜?” 그러나 이것은 Stackoverflow :)에 게시해야 할 또 다른 질문입니다.

  2. Focus 관련 문제를 추적하는 가장 좋은 방법은 .Net 소스 코드를 디버깅하는 것입니다. 농담이 없습니다. 많은 시간을 절약했습니다. .net 소스 코드 디버깅을 활성화하려면 Shawn Bruke의 블로그를 참조하십시오 .

  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를 참조하지 않는 경우.

건배.


답변

나는이 질문이 지금까지 천 번 이상 대답되었다는 것을 알고 있지만, 비슷한 문제가있는 다른 사람들을 도울 것으로 생각되는 Anvaka의 기여를 약간 수정했습니다.

먼저 위의 첨부 속성을 다음과 같이 변경했습니다.

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 ((bool)e.NewValue)
        {
            fe.Focus();
        }
    }

    private static void fe_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var fe = (FrameworkElement)sender;
        if (fe.IsVisible && (bool)((FrameworkElement)sender).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);
    }
}

가시성 참조를 추가 한 이유는 탭이었습니다. 처음에 보이는 탭 이외의 다른 탭에서 연결된 속성을 사용한 경우 컨트롤에 수동으로 초점을 맞출 때까지 연결된 속성이 작동하지 않은 것 같습니다.

다른 장애물은 기본 속성이 포커스를 잃었을 때 거짓으로 재설정하는보다 우아한 방법을 만드는 것이 었습니다. 이곳에서 잃어버린 초점 이벤트가 시작되었습니다.

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

가시성 문제를 처리하는 더 좋은 방법이 있으면 알려주세요.

참고 : BindsTwoWayByDefault를 DependencyProperty에 넣을 것을 제안한 Apfelkuacha에게 감사드립니다. 나는 오래 전에 내 자신의 코드로 수행했지만이 게시물을 업데이트하지 않았습니다. 이 변경으로 인해 WPF 코드에서 더 이상 Mode = TwoWay가 필요하지 않습니다.


답변

가장 좋은 방법은 MVVM 원칙을 깨끗하게 유지하는 것이므로 기본적으로 MVVM Light와 함께 제공된 메신저 클래스를 사용해야하며 사용 방법은 다음과 같습니다.

viewmodel (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 표준을 유지하면서도 훌륭합니다.


답변

이것들 중 어느 것도 나를 위해 정확하게 일한 것은 아니지만 다른 사람들의 이익을 위해 이미 여기에 제공된 코드 중 일부를 기반으로 작성했습니다.

사용법은 다음과 같습니다.

<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
}


답변

이것은 오래된 스레드이지만 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는 실제로 이런 식으로 사용되도록 의도되지 않았으므로 코드 숨김이 갈 길입니다.


답변

필자의 경우 OnIsFocusedPropertyChanged 메서드를 변경할 때까지 FocusExtension이 작동하지 않았습니다. 원래는 중단 점이 프로세스를 중지했을 때 디버그에서만 작동했습니다. 런타임에 프로세스가 너무 빠르며 아무 일도 일어나지 않았습니다. 이 작은 수정과 친구 작업의 도움으로 두 시나리오에서 모두 잘 작동합니다.

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);
  }
}


답변

문제는 IsUserNameFocused가 true로 설정되면 false가되지 않는다는 것입니다. 이를 통해 FrameworkElement의 GotFocus 및 LostFocus를 처리하여 문제를 해결합니다.

소스 코드 형식에 문제가 있었으므로 여기에 링크가 있습니다.