使用MVVM在设置后重置组合框的选定项

13

我在我的WPF应用程序中使用ComboBox,并遵循MVVM模式。我有一组字符串列表,希望在ComboBox中显示。

XAML:

<ComboBox ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding SelectedItem}" />

视图模型:

public Collection<string> ItemsCollection; // Suppose this has 10 values.
private string _selectedItem;
public string SelectedItem
{
    get { return _selectedItem; }
    set
    {
        _selectedItem = value;
        Trigger Notify of property changed.
    }
}

现在这段代码完全正常工作。我可以从视图中进行选择,并且在ViewModel中获取更改,如果我从我的ViewModel更改SelectedItem,我可以在视图中看到它。

现在这里是我想要实现的。当我从视图中更改选定的项时,我需要进行检查,如果值为good/bad(或任何其他值),则设置选定的项,否则不要设置。所以我的视图模型更改如下。

public string SelectedItem
{
    get { return _selectedItem; }
    set
    {
        if (SomeCondition(value))
            _selectedItem = value;           // Update selected item.
        else
            _selectedItem = _selectedItem;   // Do not update selected item.
        Trigger Notify of property changed.
    }
}

现在,当我执行这段代码并且SomeCondition(value)返回false时,SelectedItem返回旧的字符串值,但是在我的视图中ComboBox中选择的项是我选择的值。所以假设我在ComboBox中有10个字符串值。除了第二个和第四个元素(SomeCondition返回2nd和4th值的false),所有值都很好。我希望如果我选择第二个或第四个元素,则selectedItem不会更改。但我的代码没有正确地执行此操作。如果我选择第二个元素,则视图仍显示第二个元素为选定状态。我知道我的代码有问题,但是是什么呢?


1
那不是一个非常用户友好的设计。如果我在组合框中选择了某个东西,我希望那就是我的选择项。你应该从组合框中删除无效选项。如果选择的有效性基于另一个UI元素的选定值,则更改该选择应触发重建组合框的ItemSource。 - Lee O.
1
这是显示所有项目的要求,不能更改。 - fhnaseer
1
我同意不显示无效选项,或将它们变灰并禁用选择,这就是 UI 的作用。 - Mark Homer
让您的视图选择第二项,但使用IDataErrorInfo显示错误。这样,您可以从setter中删除“验证”,并将其放入IDataErrorInfo中。但是,如果您将最后一个“好”的值设置为selecteditem并执行OnPropertyChanged(),它仍然应该起作用。 - blindmeis
5个回答

24

这是一个非常有趣的问题。首先,我同意其他人的观点,这不是一个处理无效选择的推荐方法。正如@blindmeis建议的那样,IDataErrorInfo是解决此问题的好方法之一。

回到问题本身。满足@Faisal Hafeez要求的解决方案是:

public string SelectedItem
{
    get { return _selectedItem; }
    set
    {
        var oldItem=_selectedItem;
        _selectedItem=value;
        OnPropertyChanged("SelectedItem")

        if (!SomeCondition(value)) //If does not satisfy condition, set item back to old item
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => SelectedItem = oldItem),
                                                 DispatcherPriority.ApplicationIdle);
    }
}

Dispatcher 是一种优雅的方式,用于在另一个 UI 同步期间处理一些 UI 同步。例如,在这种情况下,您想要在选择绑定期间重置选择。

一个问题是为什么我们必须首先更新选择。那是因为 SelectedItemSelectedValue 被分别赋值,而显示在 ComboBox 上的内容不依赖于 SelectedItem(也许依赖于SelectedValue,我在这里不确定)。还有一个有趣的点是,如果 SelectedValue 改变,SelectedItem 必须也跟着改变,但是 SelectedItem 在改变时并没有更新 SelectedValue。因此,你可以选择绑定到 SelectedValue ,这样就不必进行首次赋值了。


我们遇到了完全相同的问题,你的解决方法有效。 我们也想显示所有项目,尽管不可能选择其中一个。而且我们不想禁用控件,因为在短时间内无法选择另一个项目。 是否有一种方法可以编写行为,以确保选择器UI元素始终显示正确的属性值? - Lumo
对我来说,这会导致原先选择的值稍微出现一些“闪烁”,但已经足够好用了。 - Ddddan
我也非常感激这个解决方案。看起来正确的答案是让框架将“value”设置为已传递,然后将其重置为原始值。但是,有人能解释一下为什么OP的解决方案不起作用吗(在通知之前进行测试和分配)? - msr

12

我知道我有点晚了,但是从WPF 4.5开始,你可以像这样使用Delay命令:

    <ComboBox ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, Delay=1, UpdateSourceTrigger=PropertyChanged}" />

这篇文章在我花了几个小时查找资料之后帮了我很大的忙。如果您想了解其他可能有效的方法,您可以阅读这篇文章及其评论。


谢谢!我因为这个问题搜索了好几个小时,而 Delay 就像我想要的那样解决了它! - Catarina Ferreira
和卡塔琳娜一样,我也搜索了好几个小时,这个完美无缺,谢谢你!!! - Silent
这个简单的解决方案实际上非常有效!(尽管Delay在这种情况下并不是很直观的帮助) - undefined

3
尝试将XAML更改为以下内容:
<ComboBox ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

"Mode=TwoWay" 这个是必须的吗?我尝试在我的视图模型构造函数中设置默认值,这样可以正常工作。但是在 setter 中设置它会失败。 - fhnaseer
1
@FaisalHafeez 是的,因为您正在更改View和ViewModel中的属性。 - Haris Hasan

0
<ComboBox ItemsSource="{Binding ItemsCollection}" SelectedIndex="{Binding currSelection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged} " />

而且在你的虚拟机中

{
    set{ 
        currSelection = -1;
    }
}

0
如果您正在寻找一个可重用的MVVM / XAML解决方案,我为另一个线程准备了这个。它使用WPF行为,非常简单易于管理,无需外部库。只需将其复制到解决方案中即可。

ComboBox Selected Item Clear Behavior


请勿在其他 Stack Overflow 问题中发布仅链接的答案。相反,投票/标记为重复,或者如果该问题不是重复项,则量身定制答案以回应此特定问题 - Paul Roub

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