从setter更改属性并通知

时间:2017-01-06 18:00:10

标签: c# xaml windows-store-apps uwp inotifypropertychanged

在属性的设置器中,有时我必须将属性的值更改为当前正在设置的值以外的值。对于Windows Store应用程序而言,简单地提升PropertyChanged事件并不起作用。我可以更改该值,但UI不会根据该更改自行更新。

<TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

视图模型:

class MyViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string text;
    public string Text
    {
        get { return text; }
        set
        {
            if ( text != value ) {
                text = value == "0" ? "" : value;
                OnPropertyChanged();
            }
        }
    }

    protected void OnPropertyChanged( [CallerMemberName] string propertyName = null )
    {
        PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
    }
}

我编写了以下方法,可以很好地解决这个问题:

protected void OnPropertyChanged_Delayed( [CallerMemberName] string propertyName = null )
{
    Task.Delay( 1 ).ContinueWith(
        t => OnPropertyChanged( propertyName ),
        TaskScheduler.FromCurrentSynchronizationContext()
    );
}

我称之为OnPropertyChanged而不是OnPropertyChanged_Delayed。但是我最近发现我的Windows应用商店应用中出现间歇性错误,这是由用户导航离开页面后发生processhtml: { dist:{ options: { process: true, templateSettings: { evaluate: /{\{(.+?)\}\}/g, interpolate: /{\{=(.+?)\}\}/g, escape: /{\{-(.+?)\}\}/g } }, files: [ { expand: true, cwd: 'dist/', src: ['*.html'], dest: 'dist/', ext: '.html' }, ], } }, 调用引起的。这导致我寻求替代解决方案。

是否有更好的方法从其设置器中更改属性的值并通知UI更改?我目前正在开发一个Windows应用商店应用,所以我想在这种情况下给出答案。但我最终会把它移植到UWP。这也是UWP的一个问题吗?如果是这样,我也想在那里找到解决方案(可能是相同的)。

3 个答案:

答案 0 :(得分:1)

正如我在上面的评论中所解释的那样,在处理ComboBox控件时遇到了这种问题。我想更改setter中所选项的值,但视图忽略了我的新值。延迟PropertyChanged事件是一个丑陋的解决方法,可能会导致意外的副作用。

但解决此问题的另一种方法是将SelectionChanged事件绑定到视图模型中的命令。

此XAML像往常一样绑定到SelectedItem,但也使用System.Windows.Interactivity绑定SelectionChanged事件:

<ComboBox ItemsSource="{Binding MyItems}"
          SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

在视图模型中,不要尝试覆盖setter中的选定值。相反,在ICommand实现中更改它:

string _selectedItem;
public string SelectedItem
{
    get { return _selectedItem; }
    set
    {
        _selectedItem = value;
        PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
    }
}

// RelayCommand is just a custom ICommand implementation which calls the specified delegate when the command is executed
public RelayCommand SelectionChangedCommand
{
    get
    {
        return new RelayCommand(unused =>
        {
            if (_selectedItem == MyItems[2])
                _selectedItem = MyItems[0];
            PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
        });
    }
}

正如我所说,我只是观察了SelectedItem属性的这种行为。如果您遇到TextBox问题,那么您可以尝试更改绑定的UpdateSourceTrigger:

<TextBox Text={Binding MyText, Mode=TwoWay, UpdatedSourceTrigger=PropertyChanged} />

答案 1 :(得分:0)

我在UWP中这样做没有问题。我不知道Windows Store应用程序有什么不同。

{{1}}

您的财产是什么样的?

答案 2 :(得分:0)

我的问题是:从属性的setter中,如何更改正在设置的值并让UI识别出该更改?

双向XAML绑定假定设置为源的目标值也是源实际存储的值。它忽略了它刚刚设置的属性的PropertyChanged事件,因此在更新后它不会检查源的实际值。

文本框

最简单的解决方案是:单向绑定,而是在TextChanged处理程序中更新源。

<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
         TextChanged="TextBox_TextChanged"/>

事件处理程序:

void TextBox_TextChanged( object sender, TextChangedEventArgs e )
{
    var tb = (TextBox)sender;
    var pm = (MyViewModel)DataContext;
    pm.Text = tb.Text; //update one-way binding's source
}

当UI的文本发生变化时,通过设置视图模型的文本来处理事件。视图文本的单向绑定意味着它接收实际设置的值。这样做可以避免上面提到的XAML绑定的限制。

可以将绑定保留为双向,上述解决方案仍然有效。但是如果你想让双向绑定更新它的源代码,那么代码会稍微长一些:

void TextBox_TextChanged( object sender, TextChangedEventArgs e )
{
    var tb = (TextBox)sender;
    var pm = (MyViewModel)DataContext;
    tb.GetBindingExpression( TextBox.TextProperty ).UpdateSource();
    if ( tb.Text != pm.Text )
        tb.Text = pm.Text; //correct two-way binding's target
}

如果您不需要UpdateSourceTrigger=PropertyChanged,则另一种选择是:

<TextBox Text="{Binding Text, Mode=TwoWay}" LostFocus="TextBox_LostFocus"/>

使用事件处理程序:

void TextBox_LostFocus( object sender, RoutedEventArgs e )
{
    var tb = (TextBox)sender;
    var pm = (MyViewModel)DataContext;
    tb.GetBindingExpression( TextBox.TextProperty ).UpdateSource();
    pm.OnPropertyChanged( "Text" );
}

组合框

@ RogerN的回答有助于显示使用SelectionChanged可以解决问题,但他实际上并没有显示如何更改setter中的值。我会在这里展示一下。

<ComboBox ItemsSource="{Binding Items}"
          SelectedItem="{Binding Text, Mode=TwoWay}"
          SelectionChanged="ComboBox_SelectionChanged"/>

将此属性添加到视图模型:

public IList<string> Items
{
    get { return items; }
}
readonly IList<string> items = new ObservableCollection<string> { "first", "second", "third", "forth" };

Text属性的setter中,更改值如下:

text = value == "third" ? "forth" : value;

上面显示的TextBox的第一种方法不适用于ComboBox,但第二种方法适用。{/ p>

void ComboBox_SelectionChanged( object sender, SelectionChangedEventArgs e )
{
    var cb = (ComboBox)sender;
    var pm = (MyViewModel)DataContext;
    cb.GetBindingExpression( ComboBox.SelectedItemProperty ).UpdateSource();
    if ( (string)cb.SelectedItem != pm.Text )
        cb.SelectedItem = pm.Text; //correct two-way binding's target
}