具有绑定的选定值和静态项目的WPF组合框无法在初始化时识别选择

时间:2018-09-12 17:05:07

标签: wpf data-binding

我需要一个带有两个值的组合框。第一个应具有自定义名称,而第二个应使用基础绑定对象的属性。这两项都是VM上的值,我能够成功绑定所有这些值。

XAML

<Window x:Class="StaticComboBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StaticComboBox"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:StaticUIVm}"
        Title="MainWindow"
        Height="450"
        Width="800">
   <Grid>
      <Grid.RowDefinitions>
         <RowDefinition />
         <RowDefinition Height="Auto"/>
         <RowDefinition />
      </Grid.RowDefinitions>
      <ComboBox Grid.Row="1"
         SelectedValuePath="Tag"
                SelectedValue="{Binding SelectedValue, Mode=TwoWay}">
         <ComboBox.Items>
            <ComboBoxItem Content="Custom Display Text 111"
                          Tag="{Binding FirstValue}" />
            <ComboBoxItem Content="{Binding SecondValue.Item2}"
                          Tag="{Binding SecondValue}" />
         </ComboBox.Items>
      </ComboBox>
   </Grid>
</Window>

XAML.cs

using System.Windows;

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

StaticUIVm.cs

using StaticComboBox.Annotations;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace StaticComboBox
{
   public class StaticUIVm : INotifyPropertyChanged
   {
      public Tuple<long, string> FirstValue { get; set; }

      public Tuple<long, string> SecondValue { get; set; }

      private Tuple<long, string> _selectedValue;

      public Tuple<long, string> SelectedValue
      {
         get { return _selectedValue; }
         set
         {
            _selectedValue = value;
            OnPropertyChanged();
         }
      }

      public StaticUIVm()
      {
         FirstValue = new Tuple<long, string>(1, "Some Static Value");
         SecondValue = new Tuple<long, string>(2, "Some Other Static Value");

         SelectedValue = FirstValue;
      }

      public event PropertyChangedEventHandler PropertyChanged;

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

我的问题是,尽管绑定对于项目正确工作并且显示正确,并且当我作为用户选择值时,组合框在初始化VM类时并未反映正确的选择。意思是,它没有选择FirstValue。这对我来说没有意义,因为引用应该完全相同,并且我已经确认在初始化过程中虚拟机上的值实际上在变化。我肯定在构造函数中初始化了值,并尊重它们并在加载时显示了这些值,因此我对这里要出错的地方有些困惑。

编辑

我已经接受了mm8的答案,但不得不对XAML进行一些其他调整,以使其能够按需运行。我需要能够根据项目的ID值(在运行时设置)触发自定义文本。因此,一个简单的DataTrigger无法正常工作,因此我不得不使用MultiBinding。当选择一个项目时,MultiBinding破坏了显示(如ComboBox.ItemTemplate not displaying selection properly中所述),因此我不得不将IsEditable设置为false。完整的组合框在下面。

  <ComboBox Grid.Row="2"
            Grid.Column="1"
            IsEditable="False"
            ItemsSource="{Binding ItemSource}"
            SelectedItem="{Binding SelectedValue}">
     <ComboBox.ItemTemplate>
        <DataTemplate>
           <TextBlock>
              <TextBlock.Style>
                 <Style TargetType="TextBlock">
                    <Setter Property="Text"
                            Value="{Binding Name}" />
                    <Style.Triggers>
                       <DataTrigger Value="True">
                          <DataTrigger.Binding>
                             <MultiBinding Converter="{StaticResource LongEqualToLongMultiBindingDisplayConverter}">
                                <Binding Path="Id" />
                                <Binding Path="DataContext.FirstValue.Id" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MyControl}}" />
                             </MultiBinding>
                          </DataTrigger.Binding>
                          <Setter Property="Text"
                                  Value="Custom Display Text 111" />
                       </DataTrigger>
                    </Style.Triggers>
                 </Style>
              </TextBlock.Style>
           </TextBlock>
        </DataTemplate>
     </ComboBox.ItemTemplate>
  </ComboBox>

此XAML与mm8答案的建议(建立一个运行时根据提供的两个值初始化的集合)相结合,可以解决问题。

2 个答案:

答案 0 :(得分:1)

为什么不简单地从视图模型中公开可选项的集合?这是使用MVVM解决此问题的方法:

public class StaticUIVm : INotifyPropertyChanged
{
    public Tuple<long, string> FirstValue { get; set; }
    public Tuple<long, string> SecondValue { get; set; }

    private Tuple<long, string> _selectedValue;
    public Tuple<long, string> SelectedValue
    {
        get { return _selectedValue; }
        set
        {
            _selectedValue = value;
            OnPropertyChanged();
        }
    }

    public IEnumerable<Tuple<long, string>> Values { get; }

    public StaticUIVm()
    {
        FirstValue = new Tuple<long, string>(1, "Some Static Value");
        SecondValue = new Tuple<long, string>(2, "Some Other Static Value");
        Values = new Tuple<long, string>[2] { FirstValue, SecondValue };
        SelectedValue = SecondValue;
    }

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

XAML:

<ComboBox x:Name="cmb" Grid.Row="1" ItemsSource="{Binding Values}"
                  SelectedItem="{Binding SelectedValue}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock>
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Setter Property="Text" Value="{Binding Item2}" />
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Item1}" Value="1">
                                <Setter Property="Text" Value="Custom Display Text 111" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

您甚至可以删除FirstValueSecondValue属性。自定义文本在视图中定义,但实际选择的选项在视图模型中定义。

答案 1 :(得分:0)

主要编辑:

因此,当我第一次添加答案时,我试图使用您已经拥有的内容,而不是演示如何做。 WPF在某些方面既灵活又狭窄,我经常发现自己在解决问题。当使用其他方法完成时,您的问题实际上非常简单。我的许多程序都具有ResponseEntity<String>控件,尽管我通常使用集合来填充,但可以通过在ComboBox上使用辅助数据类来应用类似的原理。这将显着增加灵活性,并且更加健壮。

我添加了属性Tuple并将其绑定到您的datacontext类中的SelectedIndex。我也将int更改为SelectedValue,因为在此用例中它要优越得多。

SelectedItem

数据上下文类,数据类和扩展方法:

因此,我将您的属性更改事件移到了单独的类。我建议这样做,因为它可以重用。对于数据类尤其方便。现在,在构造函数中,我们设置选定的项目和选定的索引。

    <ComboBox Grid.Row="1"
              SelectedValuePath="Tag"
              SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
              SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}">

        <ComboBox.Items>
            <ComboBoxItem Content="{Binding FirstValue.display}"
                          Tag="{Binding FirstValue}" />
            <ComboBoxItem Content="{Binding SecondValue.display}"
                          Tag="{Binding SecondValue}" />
        </ComboBox.Items>
    </ComboBox>

您可能会问所有这些的原因...那么,这增加了灵活性,而不是“黑客”事情或通过数据触发器在XAML中增加了额外的复杂性。您正在使用易于操作的数据类纯粹是在逻辑之外工作。