使用MVVM框架

时间:2016-07-24 21:22:20

标签: c# wpf xaml mvvm combobox

好的,

我看过几个类似的问题,但过去几天都无法弄清楚这个问题。我有两个组合框,我希望每个组合框在另一个组合框中隐藏所选元素。例如,如果我在ComboBox 1中选择一个值,那么选择项应作为ComboBox 2中的选项删除。

我考虑过使用命令,但ComboBoxes没有命令。我已经粘贴在组合框的XAML和ViewModel代码下面。我很感激任何帮助。我知道下面的代码是错误的,但我认为这个逻辑应该在有限的ItemSource的setter中。

                <ComboBox Margin="0,7,0,0"
                          Name="ComboBoxA"
                          HorizontalAlignment="Stretch"
                          Header="{Binding AccountHeader}"
                          ItemTemplate="{StaticResource ComboBoxTemplate}"
                          ItemsSource="{Binding ChargedAccounts,
                                                Mode=TwoWay,
                                                UpdateSourceTrigger=PropertyChanged}"

                          SelectedItem="{Binding SelectedAccount,
                                                 Mode=TwoWay,
                                                 UpdateSourceTrigger=PropertyChanged}" />

                <ComboBox x:Uid="TargetAccountTextBox"
                          Name="ComboBoxB"
                          Margin="0,7,0,0"
                          HorizontalAlignment="Stretch"
                          Header="target account"
                          ItemTemplate="{StaticResource ComboBoxTemplate}"
                          ItemsSource="{Binding TargetAccounts,
                                                Mode=TwoWay,
namespace MoneyFox.Shared.ViewModels
{
    [ImplementPropertyChanged]
    public class ModifyPaymentViewModel : BaseViewModel, IDisposable
    {
        private readonly IDefaultManager defaultManager;
        private readonly IDialogService dialogService;
    private readonly IPaymentManager paymentManager;

    //this token ensures that we will be notified when a message is sent.
    private readonly MvxSubscriptionToken token;
    private readonly IUnitOfWork unitOfWork;

    // This has to be static in order to keep the value even if you leave the page to select a category.
    private double amount;
    private Payment selectedPayment;

    public ModifyPaymentViewModel(IUnitOfWork unitOfWork,
        IDialogService dialogService,
        IPaymentManager paymentManager,
        IDefaultManager defaultManager)
    {
        this.unitOfWork = unitOfWork;
        this.dialogService = dialogService;
        this.paymentManager = paymentManager;
        this.defaultManager = defaultManager;

        TargetAccounts = unitOfWork.AccountRepository.Data;
        ChargedAccounts = unitOfWork.AccountRepository.Data;
        token = MessageHub.Subscribe<CategorySelectedMessage>(ReceiveMessage);
    }


    ObservableCollection<Account> _SelectedAccount;
    ObservableCollection<Account> SelectedAccount
    {

        get
        {
            return _SelectedAccount;
        }
        set
        {

            _SelectedAccount = value;
            for(int i = 0; i < ChargedAccounts.Count; i++)
            {
                if(ChargedAccounts[i].ToString() == _SelectedAccount.ToString())
                {
                    ChargedAccounts.Remove(ChargedAccounts[i]);
                }
            }

        }

    }

    ObservableCollection<Account> _TargetAccount;
    ObservableCollection<Account> Targetccount
    {

        get
        {
            return _SelectedAccount;
        }
        set
        {

            _SelectedAccount = value;
            for (int i = 0; i < TargetAccounts.Count; i++)
            {
                if (TargetAccounts[i].ToString() == _SelectedAccount.ToString())
                {
                    TargetAccounts.Remove(ChargedAccounts[i]);
                }
            }

        }

    }

2 个答案:

答案 0 :(得分:0)

虽然我同意Ed提供的答案中的许多要点,但有一种更简单的方法可以在没有DataTriggers或转换器的情况下执行此操作。框架中已经有一个可过滤的CollectionViewSource是你的朋友(Scott Hanselman loves it

我会将ComboBoxA绑定到您的常规ChargedAccounts属性,但我会将ComboBoxB修改为:

  • 绑定到返回ICollectionView
  • 的View后面代码中的属性
  • 在ComboBoxA的SelectionChanged事件处理程序中(也在视图后面的代码中)我会调整ICollectionView的过滤器以排除当前选定的项目

粗略地说,这可以在几行中完成:

public ICollectionView FilteredData { get; set; }

private void ComboBoxA_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var z = new CollectionViewSource {Source = ViewModel.ChargedAccounts.Where(p => p != ViewModel.SelectedAccount) };
    FilteredData = z.View;
}

当然,假设您在视图后面的代码中具有ViewModel属性,最好将其作为接口公开,并且ChargedAccountsSelectedAccount属性为可通过该界面获得 您还可以在viewmodel中将这几行拼凑在一起并通过SelectedAccount上的属性更改触发它 - 我只是认为响应UI操作的过滤操作应该放在UI后面的代码中,但那个决定真的取决于你。

答案 1 :(得分:-1)

为组合框提供一个带有数据触发器的ItemContainerStyle(TargetType="ComboBoxItem")。对于ComboBoxA,这看起来像这样:

<ComboBox
    ...
    x:Name="ComboBoxA"
    ...
    >
    <ComboBox.ItemContainerStyle>
        <Style TargetType="ComboBoxItem">
            <Style.Triggers>
                <DataTrigger Value="True">
                    <DataTrigger.Binding>
                        <MultiBinding 
                            Converter="{local:ObjectEquals}"
                            >
                            <Binding 
                                Path="SelectedItem" 
                                ElementName="ComboBoxB" />
                            <!-- Binding with no properties just binds to the DataContext -->
                            <Binding />
                        </MultiBinding>
                    </DataTrigger.Binding>
                    <Setter 
                        Property="Visibility" 
                        Value="Collapsed" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ComboBox.ItemContainerStyle>
</ComboBox>

ComboBoxB获得相同的优惠,但ElementName="ComboBoxA"约束中的SelectedItem

我们需要编写多值转换器。这很简单:

public class ObjectEquals : MarkupExtension, IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values.Length == 2 && values[0] == values[1];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

如果可以将DataTrigger.Value绑定到{Binding},那将非常方便,但它不是依赖属性。

你*也可以在viewmodel中暂时删除SelectedAccount中的TargetAccounts - 你有一个私有的完整_targetAccountsFull列表,以及一个公开过滤的列表。 SelectedAccount的setter会过滤列表。你准备这样做了吗?

但这不是我想要的好解决方案。隐藏组合框项目是UI设计的东西; viewmodel不应该参与其中,事实上甚至不应该意识到这样的事情发生了。 WPF / MVVM的一个乐趣是你可以将这些东西分离成视图中的纯UI代码。 viewmodel有其自身的复杂性需要担心。

顺便说一下,您将SelectedItem绑定到SelectedAccount,但SelectedAccountObservableCollection。这是没有意义的。有一个选定的帐户。使它成为一个Account,而不是它们的集合。