根据组合框中的选择将属性绑定到两个控件

时间:2015-02-15 08:46:25

标签: c# wpf xaml

我是WPF的新手。我有一个ComboBox和TextBox都应该根据组合框中的选择绑定到单个属性。如果我选择'其他',则属性应该绑定到textbox else to combobox。有人可以指导我。

我很抱歉没有说清楚。我有一个属性" Name"的对象。我是WPF的新手,这是我第一次使用WPF。我真的不确定这是不是正确的方式。请帮帮我。

Xaml文件:

<mui:ModernWindow x:Class="TestOtherInBinding.MainWindow"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mui="http://firstfloorsoftware.com/ModernUI"
              Title="mui" 
              Style="{StaticResource BlankWindow}" Loaded="ModernWindow_Loaded">
<ScrollViewer>
    <StackPanel Name="spTest">
        <ComboBox Name="cmbTest" Width="140" Margin="5" SelectionChanged="cmbTest_SelectionChanged" SelectedValuePath="Content" >
            <ComboBoxItem Content="Name1"/>
            <ComboBoxItem Content="Name2"/>
            <ComboBoxItem Content="Name3"/>
            <ComboBoxItem Content="Name4"/>
            <ComboBoxItem Content="Other"/>
        </ComboBox>
        <TextBox Name="txtTest"  Width="140" Margin="5">
        </TextBox>
        <Button Content="Submit" Width="80" />
    </StackPanel>

</ScrollViewer>

代码背后:

public partial class MainWindow : ModernWindow
{
    public MainWindow()
    {
        InitializeComponent();

    }
    public string MyProperty { get; set; }

    Binding bin = new Binding("Name");

    private void cmbTest_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        bin.ValidationRules.Add(new ExceptionValidationRule());

        if (cmbTest.SelectedValue.ToString() == "Other")
        {
            txtTest.Visibility = Visibility.Visible;
            BindingOperations.ClearBinding(cmbTest, ComboBox.TextProperty);
            BindingOperations.SetBinding(txtTest, TextBox.TextProperty, bin);
        }
        else
        {
            txtTest.Visibility = Visibility.Collapsed;
            BindingOperations.ClearBinding(txtTest, TextBox.TextProperty);
            BindingOperations.SetBinding(cmbTest, ComboBox.TextProperty, bin);
        }
    }

    private void ModernWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Peron p = new Peron();
        spTest.DataContext = p;
        txtTest.Visibility = Visibility.Collapsed;
        BindingOperations.SetBinding(cmbTest, ComboBox.TextProperty, bin);

        if (p.Name != string.Empty)
            cmbTest.SelectedIndex = 0;
    }
}

对象:

class Peron
{
    string name;
    public string Name
    {
        get
        {  return name;  }
        set
        {
            if (value == string.Empty)
            {
                throw new Exception("Name Should not be empty");
            }
        }
    }

When i changed the selection to "Other", clearbinding on combobox is throwing exception.

谢谢, 拉姆

3 个答案:

答案 0 :(得分:2)

我不确定如何改进这一点,但例外显然与此行有关: BindingOperations.SetBinding(txtTest, TextBox.TextProperty, bin);

更改ComboBox的Text时,SelectedValue设置为null,再次调用cmbTest_SelectionChanged并抛出异常。

建议:

  1. 您可能希望修改ComboBox的ControlTemplate,创建支持某种叠加文本的自定义组合框。
  2. 不要手动更改组合框的文本,而是使用anotherText:

    <Grid> <ComboBox .../> <Border Background="White"> <TextBlock x:Name="anotherText"/> </Border> </Grid>


  3. 上一个答案:

    您可以在ViewModel中使用每个DependencyProperty(DP)的PropertyChangedCallback,以便修改ViewModel属性并检查高级条件。

    首先创建一个ViewModel(名为MainVm的空类)。然后将MainWindow的DataContext设置为它的实例。

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainVm();
    }
    

    MainVm有四个关键DP:

    1. SelectedItem(绑定到ComboBox.SelectedItem)
    2. IsLastItemSelected(绑定到最后一个ComboBoxItem&#39; IsSelected)
    3. 文本(绑定到TextBox.Text)
    4. 结果(输出)
    5. 以便MainVm看起来像这样:

      public class MainVm : DependencyObject
      {
      
          /// <summary>
          /// Gets or sets a bindable value that indicates ComboBox SelectedItem
          /// </summary>
          public object SelectedItem
          {
              get { return (object)GetValue(SelectedItemProperty); }
              set { SetValue(SelectedItemProperty, value); }
          }
          public static readonly DependencyProperty SelectedItemProperty =
              DependencyProperty.Register("SelectedItem", typeof(object), typeof(MainVm),
              new PropertyMetadata(null, (d, e) =>
              {
                  //property changed callback
                  var vm = (MainVm)d;
                  var val = (object)e.NewValue;
                  if(val!=null && !vm.IsLastItemSelected )
                      //Result =  SelectedItem,   if the last item is not selected
                      vm.Result = val.ToString();
              }));
      
      
          /// <summary>
          /// Gets or sets a bindable value that indicates custom Text
          /// </summary>
          public string Text
          {
              get { return (string)GetValue(TextProperty); }
              set { SetValue(TextProperty, value); }
          }
          public static readonly DependencyProperty TextProperty =
              DependencyProperty.Register("Text", typeof(string), typeof(MainVm),
              new PropertyMetadata("", (d, e) =>
              {
                  //property changed callback
                  var vm = (MainVm)d;
                  var val = (string)e.NewValue;
                  //Result =  Text,           if last item is selected
                  //          SelectedItem,   otherwise
                  vm.Result = vm.IsLastItemSelected ? val : vm.SelectedItem.ToString();
              }));
      
      
          /// <summary>
          /// Gets or sets a bindable value that indicates whether the 
          ///   LastItem of ComboBox is Selected
          /// </summary>
          public bool IsLastItemSelected
          {
              get { return (bool)GetValue(IsLastItemSelectedProperty); }
              set { SetValue(IsLastItemSelectedProperty, value); }
          }
          public static readonly DependencyProperty IsLastItemSelectedProperty =
              DependencyProperty.Register("IsLastItemSelected", typeof(bool), typeof(MainVm),
              new PropertyMetadata(false, (d, e) =>
              {
                  //property changed callback
                  var vm = (MainVm)d;
                  var val = (bool)e.NewValue;
                  //Result =  Text,           if last item is selected
                  //          SelectedItem,   otherwise
                  vm.Result = val ? vm.Text : vm.SelectedItem.ToString();
              }));
      
      
          /// <summary>
          /// Gets or sets a bindable value that indicates Result
          /// </summary>
          public string Result
          {
              get { return (string)GetValue(ResultProperty); }
              set { SetValue(ResultProperty, value); }
          }
          public static readonly DependencyProperty ResultProperty =
              DependencyProperty.Register("Result", typeof(string),
              typeof(MainVm), new PropertyMetadata("select something..."));
      }
      

      现在您可以将这些DP绑定到您的视图:

      <StackPanel>
          <ComboBox Name="cmbTest" Width="140" Margin="5" SelectedItem="{Binding SelectedItem}">
              <ComboBoxItem Content="Name1"/>
              <ComboBoxItem Content="Name2"/>
              <ComboBoxItem Content="Name3"/>
              <ComboBoxItem Content="Name4"/>
              <ComboBoxItem Content="Other" IsSelected="{Binding IsLastItemSelected}"/>
          </ComboBox>
          <TextBox Width="140" Margin="5" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
          <TextBlock Text="{Binding Result}"/>
      </StackPanel>
      

答案 1 :(得分:1)

由于cmbTest.SelectedValue属性为null,您获得的异常发生是因为当用户选择&#34;其他&#34;在ComboBox中,执行以下语句:

BindingOperations.ClearBinding(cmbTest, ComboBox.TextProperty);

即。当您清除绑定到ComboBox的{​​{1}}属性时,绑定系统会将属性值重置为Text。这会导致您的事件处理程序以递归方式调用,但这次null属性为null,并且取消引用会导致SelectedValue

您可以在尝试取消引用之前检查NullReferenceException的属性值来解决此问题。但是虽然这可以避免异常,但它不会改变这样一个事实,即动态更改绑定会导致各种绑定值发生变化,这会以各种不合需要的方式发生。

我认为你应该尝试与绑定系统本身更紧密地合作以产生预期的结果,而不是试图让你当前的方法工作。


一种方法是遵循我的评论中的建议:

  

&#34;将nullComboBox中的每一个简单地绑定到两个单独的属性并解析&#34;其他&#34;是否合适?完全在代码隐藏方面,而不是通过额外的绑定逻辑?&#34;

事实上,这实际上是Bizz答案中提供的方法。恕我直言,他的答案是有用的,值得一看。也就是说,他实现的方式与我的方式有所不同,所以我将分享我的特定方法的版本:

<强> XAML:

TextBox

<强> C#:

<Window x:Class="TestSO28524422ComboAndTextToProperty.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestSO28524422ComboAndTextToProperty"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <StackPanel.DataContext>
      <local:Person/>
    </StackPanel.DataContext>
    <ComboBox Name="cmbTest" Width="140" Margin="5" SelectedValuePath="Content"
              SelectedValue="{Binding ComboBoxText}">
      <ComboBoxItem Content="Name1"/>
      <ComboBoxItem Content="Name2"/>
      <ComboBoxItem Content="Name3"/>
      <ComboBoxItem Content="Name4"/>
      <ComboBoxItem Content="Other" IsSelected="{Binding IsOtherSelected}"/>
    </ComboBox>
    <TextBox Name="txtTest"  Width="140" Margin="5"
             Text="{Binding TextBoxText, UpdateSourceTrigger=PropertyChanged}">
      <TextBox.Style>
        <Style TargetType="TextBox">
          <Style.Triggers>
            <DataTrigger Binding="{Binding IsOtherSelected}" Value="False">
              <Setter Property="Visibility" Value="Collapsed"/>
            </DataTrigger>
          </Style.Triggers>
        </Style>
      </TextBox.Style>
    </TextBox>
    <TextBlock Text="{Binding Name}"/>
  </StackPanel>
</Window>

注意:

  • 正如Bizz的回答一样,我已将派生属性(在本例中为public class Person : DependencyObject { public static readonly DependencyProperty IsOtherSelectedProperty = DependencyProperty.Register("IsOtherSelected", typeof(bool), typeof(Person)); public static readonly DependencyProperty ComboBoxTextProperty = DependencyProperty.Register("ComboBoxText", typeof(string), typeof(Person), new PropertyMetadata(OnComboBoxTextChanged)); public static readonly DependencyProperty TextBoxTextProperty = DependencyProperty.Register("TextBoxText", typeof(string), typeof(Person), new PropertyMetadata(OnTextBoxTextChanged)); public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Person)); public bool IsOtherSelected { get { return (bool)GetValue(IsOtherSelectedProperty); } set { SetValue(IsOtherSelectedProperty, value); } } public string ComboBoxText { get { return (string)GetValue(ComboBoxTextProperty); } set { SetValue(ComboBoxTextProperty, value); } } public string TextBoxText { get { return (string)GetValue(TextBoxTextProperty); } set { SetValue(TextBoxTextProperty, value); } } public string Name { get { return (string)GetValue(NameProperty); } private set { SetValue(NameProperty, value); } } private static void OnComboBoxTextChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { Person person = (Person)d; string value = (string)e.NewValue; person.Name = person.IsOtherSelected ? person.TextBoxText : value; } private static void OnTextBoxTextChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { Person person = (Person)d; string value = (string)e.NewValue; person.Name = person.IsOtherSelected ? value : person.ComboBoxText; } } )绑定到Name,以便于查看更新在UI中。您可能需要或可能不需要在现实代码中执行此类操作。
  • 此方法的基础是TextBlock子类,它用作绑定的实际目标(即DependencyObject值)。您可以设置程序,使其实际上是代码隐藏逻辑的数据对象,或者(如在MVVM模式中)处理实际数据对象的绑定任务的中间人。重要的是,它是DataContext,因此它可以直接用于XAML中的绑定语法(实现DependencyObject也应该有效)。
  • 不是在代码隐藏中设置INotifyPropertyChanged,而是在XAML中设置它。恕我直言,如果可以在XAML中处理某些内容而不是代码隐藏,那么最好这样做。这允许XAML编辑器和编译器更好地了解实现细节,并以Intellisense或编译时错误的形式提供更好的反馈。
  • 负责将相应的UI选择映射到DataContext属性的部分是Name方法,PropertyChangedCallbackOnComboBoxTextChanged()。注册这些属性的OnTextBoxTextChanged()值时,会为属性提供这些回调方法,并在属性值发生更改时调用。每个属性都根据DependencyProperty属性的当前值更新Name属性(绑定系统本身自动保持最新,它已绑定到IsOtherSelected属性您IsSelected)中的最后一项。
  • 请注意,实际上并不需要响应ComboBox属性中的更改。我们知道只有当IsOtherSelected属性发生更改时,属性才会更改,并且对该属性的更改将更新SelectedValue属性值。
  • 原始代码所做的一件事是将Name的{​​{1}}设置为Visibility,除非&#34;其他&#34;在TextBox中选择了值。这是通过在Collapsed ComboBox中为DataTrigger设置Style来实现的。默认TextBoxVisibility,只要触发器未被触发,它就会保持这种状态。但是如果它被触发,即Visible属性变为IsOtherSelectedfalse中的Setter将被应用,将DataTrigger值设置为{{1} }} 如预期的。如果不再满足触发器,WPF会将属性值设置回其先前的值(即Visibility)。

恕我直言,值得花些时间来理解这种方法。我理解(最近经历过这个并且事实上继续经历它),学习基于XAML /绑定的WPF哲学是令人生畏的。在WPF中经常有许多不同的方法可做,但并不总是清楚哪种方法最好。同时,更难以检测和理解XAML中的错误(提示:在调试器&#34;输出&#34;窗口中检查程序的运行时输出)。但是绑定系统可以处理基于GUI的程序中出现的许多演示场景,并且在很多情况下可以处理给定程序中场景的所有。从长远来看,使用API​​要容易得多,即使一开始就要困难得多。 :)


最后,我将分享第二种方法来完成同样的事情。您可以使用知道如何的自定义转换器创建Collapsed对象,而不是处理Visible子类本身中的映射(即通过子类中映射到第三个属性的单独绑定属性)。集成两个或多个输入值以产生单个输出值。

不幸的是,我无法弄清楚如何在XAML中配置它(我提到我还在自己学习吗?:)),但代码隐藏并不复杂。如果有办法在XAML中设置它,它将看起来非常相似:DependencyObject对象取代MultiBinding,为其设置MultiBinding属性,多个Binding对象作为子项添加到Converter对象中。事实上,这通常可以正常工作;我认为只是尝试绑定到Binding对象,而不是我尚未想到的MultiBinding子类。

无论如何,代码看起来像这样:

<强> XAML:

DataContext

<强> C#:

FrameworkElement

注意:

  • 在这种情况下,<Window x:Class="MultiBindingVersion.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MultiBindingVersion" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:ComboAndTextToTextConverter x:Key="comboAndTextToTextConverter1"/> </Window.Resources> <StackPanel x:Name="spTest"> <StackPanel.DataContext> <local:Person/> </StackPanel.DataContext> <ComboBox x:Name="cmbTest" Width="140" Margin="5" SelectedValuePath="Content"> <ComboBoxItem Content="Name1"/> <ComboBoxItem Content="Name2"/> <ComboBoxItem Content="Name3"/> <ComboBoxItem Content="Name4"/> <ComboBoxItem x:Name="otherItem" Content="Other"/> </ComboBox> <TextBox x:Name="txtTest" Width="140" Margin="5"> <TextBox.Style> <Style TargetType="TextBox"> <Style.Triggers> <DataTrigger Binding="{Binding ElementName=otherItem, Path=IsSelected}" Value="False"> <Setter Property="Visibility" Value="Collapsed"/> </DataTrigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox> <TextBlock x:Name="textBlock1" Text="{Binding Path=Name}"/> </StackPanel> </Window> 对象只需要public class Person : DependencyObject { public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Person)); public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty, value); } } } class ComboAndTextToTextConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string comboBoxText = values[1] as string; string textBoxText = values[2] as string; if (values[0] is bool && comboBoxText != null && textBoxText != null) { bool otherItemIsSelected = (bool)values[0]; return otherItemIsSelected ? textBoxText : comboBoxText; } return Binding.DoNothing; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); _SetMultibinding((Person)spTest.DataContext, Person.NameProperty, Tuple.Create((DependencyObject)otherItem, ComboBoxItem.IsSelectedProperty), Tuple.Create((DependencyObject)cmbTest, ComboBox.SelectedValueProperty), Tuple.Create((DependencyObject)txtTest, TextBox.TextProperty)); } private void _SetMultibinding(DependencyObject target, DependencyProperty property, params Tuple<DependencyObject, DependencyProperty>[] properties) { MultiBinding multiBinding = new MultiBinding(); multiBinding.Converter = (IMultiValueConverter)Resources["comboAndTextToTextConverter1"]; foreach (var sourceProperty in properties) { Binding bindingT = new Binding(); bindingT.Source = sourceProperty.Item1; bindingT.Path = new PropertyPath(sourceProperty.Item2); multiBinding.Bindings.Add(bindingT); } BindingOperations.SetBinding(target, property, multiBinding); } } 属性,只有基本的Person实现。在另一个示例中,Name对象作为绑定属性的存在(即使逻辑上它是目标)。但是使用DependencyProperty,目标确实需要成为目标。由于绑定将在代码隐藏中指定,因此XAML没有声明任何绑定(可见性触发器除外),因此Person对象不需要那些单独的属性,可以绑定XAML元素。
  • 这里有一个新班,MultiBinding。这是将接受多个输入并将它们转换为单个输出的类。您可以看到读取三个值作为输入的代码:Person和两个ComboAndTextToTextConverter值(分别为boolstring属性值)。代码对类型进行了一些最小的验证,然后根据需要映射值。
  • 窗口的代码隐藏是创建和设置ComboBox.SelectedValue的地方。我有一个简单的辅助方法,它接受目标对象和属性,以及可变数量的源对象和属性。它只是循环遍历源,将它们添加到TextBox.Text对象,然后在目标对象的属性上设置该MultiBinding对象。
  • 由于MultiBinding对象不再具有帮助器属性,包括MultiBinding属性,因此我继续并命名最后一个Person项,以便它可以在名称中绑定触发IsOtherSelected ComboBox属性。
  • 我使用了窗口资源中的转换器,但您可以轻松地将转换器放入资源中,只需创建一个新实例,即TextBox。我在这里选择的主要是我之前尝试通过XAML配置Visibility的工件(转换器 需要将其声明为资源)。


很抱歉这个冗长的答案。我想尝试用合理的细节来解释一切,即我希望自己在尝试自己解决这些问题时所拥有的那种细节。 :)我希望它有所帮助!

答案 2 :(得分:0)

如果我理解正确你想要将文本框文本绑定到在组合框中选择的项目的值,并且如果组合框项目&#34;其他&#34;选中后,文本框中的文本将设置为空。实现这一目标的一种方法。

        <StackPanel Name="spTest" >
        <ComboBox Name="cmbTest" Width="140" Margin="5" SelectedValuePath="Tag" >
            <ComboBoxItem Content="Name1" Tag="Name 1" IsSelected="True" />
            <ComboBoxItem Content="Name2" Tag="Name 2"/>
            <ComboBoxItem Content="Name3" Tag="Name 3"/>
            <ComboBoxItem Content="Name4" Tag="Name 3"/>
            <ComboBoxItem Content="Other" Tag=""/>
        </ComboBox>
        <TextBox Name="txtTest" Text="{Binding ElementName=cmbTest, Path=SelectedValue}"  Width="140" Margin="5" >
        </TextBox>
        <Button Content="Submit" Width="80" />
    </StackPanel>

或者如果您希望仅在&#34;其他&#34;在你的组合框中被选中,你可以做...

        <StackPanel Name="spTest">
        <ComboBox Name="cmbTest" Width="140" Margin="5" SelectedValuePath="Tag"  >
            <ComboBoxItem Content="Name1" Tag="Name 1" IsSelected="True" />
            <ComboBoxItem Content="Name2" Tag="Name 2"/>
            <ComboBoxItem Content="Name3" Tag="Name 3"/>
            <ComboBoxItem Content="Name4" Tag="Name 3"/>
            <ComboBoxItem Content="Other" />
        </ComboBox>
        <TextBox Name="txtTest" Text="{Binding ElementName=cmbTest, Path=SelectedValue}"  Width="140" Margin="5" >
            <TextBox.Style>
                <Style>
                    <Setter Property="TextBox.IsEnabled" Value="False" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ElementName=cmbTest,Path=SelectedValue, Mode=OneWay}" Value="{x:Null}">
                            <Setter Property="TextBox.IsEnabled" Value="True" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>
        <Button Content="Submit" Width="80" />
    </StackPanel>