为什么在使用OnPropertyChanged()时ComboBox中的SelectedItem没有设置?

huangapple go评论58阅读模式
英文:

Why SelectedItem in combobox is not set with OnPropertyChanged()?

问题

我有一个ComboBox和SelectedItem的非常奇怪的行为。

在ComboBox中选择任何其他项目时,我想要显示消息(该项目尚不可用),并将SelectedItem设置为[1]的值。

private static ObservableCollection<string> sourceTypeCollection = new ObservableCollection<string>() { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
public ObservableCollection<string> SourceTypeCollection
{
    get => sourceTypeCollection;
    set => Set(ref sourceTypeCollection, value);
}

private string selectedValue = "2";
public string SelectedValue
{
    get => selectedValue;
    set
    {
        selectedValue = value;              
        if (selectedValue != SourceTypeCollection[1])
        {
            MessageBox.Show(value + "尚不可用", "源信息", MessageBoxButton.OK);
            selectedValue = SourceTypeCollection[1];                   
        }
        Set(ref selectedValue, value);
    }
}

但是MVVM的WPF ComboBox显示了消息,但仍然将选定的值设置为与SourceTypeCollection[1]不同的值。

问题可能是什么?

public abstract class ViewModel : INotifyPropertyChanged, IDisposable
{
    public event PropertyChangedEventHandler PropertyChanged;

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

    protected virtual bool Set<T>(ref T field, T value, [CallerMemberName] string PropertyName = null)
    {
        if (Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(PropertyName);
        return true;
    }
}
英文:

I have a very strange behavior of combobox and SelectedItem.

I want to have a message when selecting any other item in the combobox message show (That is not available yet), and setting [1] value in the SelectedItem

private static ObservableCollection&lt;string&gt; sourceTypeCollection = new ObservableCollection&lt;string&gt;() { &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;7&quot;, &quot;8&quot;, &quot;9&quot; };
public ObservableCollection&lt;string&gt; SourceTypeCollection
{
    get =&gt; sourceTypeCollection;
    set =&gt; Set(ref sourceTypeCollection, value);
}


private string selectedValue = &quot;2&quot;;
public string SelectedValue
{
    get =&gt; selectedValue;
    set
    {
        selectedValue = value;              
        if (selectedValue != SourceTypeCollection[1])
        {
            MessageBox.Show(value + &quot; Is not available yet&quot;, &quot;Source Info&quot;, MessageBoxButton.OK);
            selectedValue = SourceTypeCollection[1];                   
        }
        Set(ref selectedValue, value);
    }
}

&lt;ComboBox     ItemsSource=&quot;{Binding SourceTypeCollection,  Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}&quot; 
                                                                           
                                          SelectedValue=&quot;{Binding SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}&quot; 
                                          
                                          IsSynchronizedWithCurrentItem=&quot;True&quot;                                         
                                          Height=&quot;30&quot; FontSize=&quot;18&quot; Width=&quot;150&quot;
                                          VerticalAlignment=&quot;Top&quot; Grid.Row=&quot;1&quot;
                                          HorizontalAlignment=&quot;Center&quot;&gt;
                                

                            &lt;/ComboBox&gt;

But MVVM wpf Combobox shows the message, but still sets the selected value different from SourceTypeCollection[1]

What can be the problem?

public abstract class ViewModel : INotifyPropertyChanged, IDisposable
{
    public event PropertyChangedEventHandler PropertyChanged;

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

    protected virtual bool Set&lt;T&gt;(ref T field, T value, [CallerMemberName] string PropertyName = null)
    {
        if (Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(PropertyName);
        return true;
    }
}

答案1

得分: 0

因为你没有设置属性为当前值。所以你不会触发以 SourceTypeCollection[1] 为参数的 PropertyChangedEvent。你是用用户选择的值来触发了 PropertyChangedEvent。在调用 Set(ref selectedValue, value) 之前,你已经改变了 "value"。

尝试这样做:

private string selectedValue = "2";
public string SelectedValue
{
    get => selectedValue;
    set
    {
        if (value != SourceTypeCollection[1])
        {
            MessageBox.Show(value + " 尚不可用", "来源信息", MessageBoxButton.OK);
            value = SourceTypeCollection[1];
        }
        Set(ref selectedValue, value);
    }
}
英文:

Because you don't set the property with current value. So you don't trigger PropertyChangedEvent with SourceTypeCollection[1]. You triggering PropertyChangedEvent with value(user choice). You have change "value" before calling Set(ref selectedValue, value);

try this;

 private string selectedValue = &quot;2&quot;;
    public string SelectedValue
    {
        get =&gt; selectedValue;
        set
        {
            if (value!= SourceTypeCollection[1])
            {
                MessageBox.Show(value + &quot; Is not available yet&quot;, &quot;Source Info&quot;, MessageBoxButton.OK);
                value= SourceTypeCollection[1];                   
            }
            Set(ref selectedValue, value);
        }
    }

答案2

得分: 0

我相信,但还没有将这个问题放入测试项目中,问题出在Set的实现上。您的setter逻辑导致属性的后备字段仅保存SourceTypeCollection[1]的值。任何其他值都在setter中被强制转换。

因此,当您调用Set(ref selectedValue, value);时,第一行中的相等性检查将始终返回true,并且函数总是在第一行就返回(最多除了第一次调用时会抛出异常)。

所以OnPropertyChanged从未被调用,WPF通知系统不会重新评估绑定值。
因此,您在组合框中有一个值,它不等于绑定的值。

另一方面,Set的实现基本上是正确的,每个人都是这样做的。它只是不适合您的特殊强制转换机制,实际上不允许选择任何值。

您(可能)可以像这样修复它(未经测试):

public string SelectedValue
{
    get => selectedValue;
    set
    {
        if (value != SourceTypeCollection[1])
        {
            MessageBox.Show(value + " Is not available yet", "Source Info", MessageBoxButton.OK);
            Set(ref selectedValue, SourceTypeCollection[1], true);
        }
        else
        {
            Set(ref selectedValue, value);
        }
    }
}

// ...

protected virtual bool Set<T>(ref T field, T value, bool forceNotify = false, [CallerMemberName] string PropertyName = null)
{
    if (Equals(field, value))
    {
        if (forceNotify) OnPropertyChanged(PropertyName);
        return false;
    }
    field = value;
    OnPropertyChanged(PropertyName);
    return true;
}

我认为这不是一个好的代码,因为我认为问题在UIX设计的层面。如果没有实际可选的内容,就不应该将选择列表放入组合框中。

英文:

I believe, but haven't put this one into a test project, that the problem is the implementation of Set. Your setter logic causes the property's backing field to hold only the value of SourceTypeCollection[1]. Any other value is coerced with in the setter.

So when you call Set(ref selectedValue, value); the check for equality in the first line will always return true and the function always returns already in the first line (at max with the exception of the first call).
<br/>So OnPropertyChanged is never invoked and the WPF-notification system doesn't re-evaluate the binding value.
So you have a value in the combo box that is not equal to value of the binding.

On the other hand the implementation of Set is basically correct and how everyone does it. It just doesn't fit in to your special coercion mechanism that actually doesn't allow any selection but one value.

You (probably) could fix it like this (untested):

    public string SelectedValue
    {
        get =&gt; selectedValue;
        set
        {
            if (value!= SourceTypeCollection[1])
            {
                MessageBox.Show(value + &quot; Is not available yet&quot;, &quot;Source Info&quot;, MessageBoxButton.OK);
                Set(ref selectedValue, SourceTypeCollection[1], true);
            }
            else 
            {
                Set(ref selectedValue, value);
            }
        }
    }    
   ...
    protected virtual bool Set&lt;T&gt;(ref T field, T value, bool forceNotify = false, [CallerMemberName] string PropertyName = null)
    {
        if (Equals(field, value))
        {
             if (forceNotify) OnPropertyChanged(PropertyName);
             return false;
        } 
        field = value;
        OnPropertyChanged(PropertyName);
        return true;
    }

I wouldn't this consider good code, since the problem to my opinion is on the level of the UIX design. You don't put a list of choices into a combo if there is nothing you can actually choose.

huangapple
  • 本文由 发表于 2023年5月29日 02:16:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76352965.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定