附加属性。如何在PropertyChangedCallback中使用SetValue。

4

我需要一种附加属性,可以从ViewModel设置焦点到UIElement。我已经创建了附加属性,并在PropertyChangedCallback中将焦点设置为UIElement。

private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
  if (o is UIElement)
  {
    if ((bool)e.NewValue)
    {
      (o as UIElement).Focus();
      (o as UIElement).SetValue(VoySetFocusProperty, false);
    }
  }
}

但我希望它可以像Shotgun中的触发器一样工作。我在ViewModel中将Test设置为true...它会调用MyAttachedProperties类中的PropertyChangedCallback方法,并将焦点设置为UIElement。
((o as UIElement).Focus();

并且 ViewModel 中的 Test 值返回为 false。

((o as UIElement).SetValue(VoySetFocusProperty, false);)

一切似乎都工作正常,但是 SetValue 在 ViewModel 中没有将我的值更改回来。

完整代码:

视图:

<Window x:Class="WpfAttachedProperty.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfAttachedProperty"
    Title="MainWindow" Height="127" Width="316">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <TextBox local:MyAttachedProperties.VoySetFocus="{Binding Path=Test,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Text="Focus Me"/>
    <Button Grid.Row="1" Content="Click to Focus" HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" Width="75" Command="{Binding MyCommand}" />
</Grid>

代码后台:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfAttachedProperty
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow:Window
{
  public MainWindow()
  {
    InitializeComponent();
    DataContext = new ViewModel();
  }
}

/// <summary>
/// Command Class
/// </summary>
public class DelegateCommand:ICommand
{
  private readonly Action _action;

  public DelegateCommand(Action action)
  {
    _action = action;
  }

  public void Execute(object parameter)
  {
    _action();
  }

  public bool CanExecute(object parameter)
  {
    return true;
  }

  public event EventHandler CanExecuteChanged
  {
    add
    {
    }
    remove
    {
    }
  }
}

/// <summary>
/// ViewModelClass
/// </summary>
public class ViewModel:INotifyPropertyChanged
{
  private bool _test = false;
  public bool Test
  {
    get
    {
      return _test;
    }
    set
    {
      _test = value;
      this.NotifyPropertyChanged("Test");
    }
  }
  public ICommand MyCommand
  {
    get
    {
      return new DelegateCommand(SetTestToTrue);
    }
  }
  private void SetTestToTrue()
  {
    this.Test = true;
  }

  #region INotifyPropertyChanged
  public void NotifyPropertyChanged(String PropertyName)
  {
    if (this.PropertyChanged != null)
    {
      this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
    }
  }
  public event PropertyChangedEventHandler PropertyChanged;
  #endregion
}


public class MyAttachedProperties
{
  public static Object GetVoySetFocus(DependencyObject obj)
  {
    return (Object)obj.GetValue(VoySetFocusProperty);
  }

public static void SetVoySetFocus(DependencyObject obj, Object value)
{
  obj.SetValue(VoySetFocusProperty, value);
}

public static readonly DependencyProperty VoySetFocusProperty =
    DependencyProperty.RegisterAttached("VoySetFocus", typeof(bool), typeof(MyAttachedProperties), new UIPropertyMetadata(false, new PropertyChangedCallback(VoySetFocusChanged), new CoerceValueCallback(CoerceVoySetFocus)));

private static object CoerceVoySetFocus(DependencyObject d, object baseValue)
{
  return (bool)baseValue;
}

private static void VoySetFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
  if (o is UIElement)
  {
    if ((bool)e.NewValue)
    {
      (o as UIElement).Focus();
      // Doesn't set Test to false in ViewModel
      (o as UIElement).SetValue(VoySetFocusProperty, false);
    }
  }
}
}
}

你好,Marko。
1个回答

6
问题出在这行代码上:
((o as UIElement).SetValue(VoySetFocusProperty, false);)

你应该使用SetCurrentValue 来设置 DP。
((o as UIElement).SetCurrentValue(VoySetFocusProperty, false);)
说明:

直接设置任何依赖属性的值会打破与源属性的绑定。

然而,SetCurrentValue不会破坏绑定并将值推回到源属性。以下是来自MSDN关于SetCurrentValue的解释:

该方法由组件使用,以编程方式设置其自己的属性之一的值,而不禁用应用程序对该属性的声明使用。 SetCurrentValue方法更改属性的有效值,但现有触发器,数据绑定和样式将继续工作


此外,我认为如果同步地从Viewmodel属性 setter到回调函数 PropertyChanged 进行操作,则不会将其传播回Viewmodel。

因此,我们可以通过使用 BeginInvoke 将其排队到调度程序上异步执行此操作,以便将其传播到视图模型Test属性。

(o as UIElement).Focus();
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate
{
    // Doesn't set Test to false in ViewModel
    (o as UIElement).SetCurrentValue(VoySetFocusProperty, false);
});

但我也想将ViewModel中的Test设置为false,但在执行(o as UIElement).SetCurrentValue(VoySetFocusProperty, false)之后它并没有被设置。 - tomatino
由于绑定的模式是双向的,因此它会自动将值推送到源。 - Rohit Vats
我觉得我不理解你的回答。为了让它按照我想要的方式工作,应该做出什么改变?从codeBehind调用SetCurrentValue会更改ViewModel中的Test。但在PropertyChangedCallback中它不起作用。 - tomatino
@tomatino - 我已经更新了我的答案。请检查一下,如果解决了你的问题,请接受它。祝你有美好的一天 :) - Rohit Vats

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接