繁体   English   中英

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

[英]Bind a property to two controls based on selection in combobox

我是WPF的新手。 我有一个ComboBox和TextBox都应该根据组合框的选择绑定到单个属性。 如果选择“其他”,则属性应绑定到文本框,否则应绑定到组合框。 有人可以指导我。

非常抱歉,我不清楚。 我有一个属性为“名称”的对象。 我是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");
            }
        }
    }

当我将选择更改为“其他”时,组合框上的clearbinding抛出异常。

谢谢,拉姆

我不确定是否可以改进此方法,但是异常明显与此行相关: BindingOperations.SetBinding(txtTest, TextBox.TextProperty, bin);

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

建议:

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

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


上一个答案:

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

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

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

MainVm具有四个关键DP:

  1. SelectedItem(绑定到ComboBox.SelectedItem)
  2. IsLastItemSelected(绑定到最后一个ComboBoxItem的IsSelected)
  3. 文本(绑定到TextBox.Text)
  4. 结果(输出)

这样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绑定到您的View:

<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>

由于cmbTest.SelectedValue属性为null,导致发生的异常是由于用户在ComboBox选择“其他”而执行此语句:

BindingOperations.ClearBinding(cmbTest, ComboBox.TextProperty);

即,当您清除对ComboBoxText属性的绑定时,绑定系统会将属性值重置为null 这将导致递归调用事件处理程序,但是这次SelectedValue属性为null,并且取消引用会导致NullReferenceException

您可以通过在尝试取消引用之前检查属性值是否为null来解决此问题。 但这虽然可以避免异常,但不会改变这样的事实,即动态更改绑定会以各种不希望的方式动态更改各种绑定值。

我认为,您应该尝试与绑定系统本身更紧密地合作,以产生所需的结果,而不是尝试使用当前的工作方法。


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

“将ComboBoxTextBox每一个简单地绑定到两个单独的属性,然后完全在代码后方而不是通过其他绑定逻辑来解决“其他”方面是否合适?”

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

XAML:

<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>

C#:

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;
    }
}

笔记:

  • 就像在Bizz的答案中一样,我将派生的属性(在本例中为Name )绑定到了TextBlock ,这样就很容易在UI中看到更新。 您可能需要也可能不需要在实际代码中执行类似的操作。
  • 这种方法的基础是DependencyObject子类,该子类充当绑定的实际目标(即DataContext值)。 您可以设置程序,使其实际上是代码隐藏逻辑的数据对象,或者(如在MVVM模式中一样)在中间处理实体数据对象的绑定任务。 重要的是,它是一个DependencyObject ,因此可以直接在XAML中的绑定语法中使用(实现INotifyPropertyChanged应该起作用)。
  • 而不是在后面的代码中设置DataContext ,而是在XAML中在此处设置它。 恕我直言,如果可以在XAML中处理某些内容而不是在背后的代码中进行处理,则最好这样做。 这使XAML编辑器和编译器可以更多地了解实现细节,并以Intellisense或适当的编译时错误的形式提供更好的反馈。
  • 将适当的UI选择映射到您的Name属性的部分是PropertyChangedCallback方法, OnComboBoxTextChanged()OnTextBoxTextChanged() 当注册这些属性的DependencyProperty值时,将为这些属性提供这些回调方法,并在属性值更改时被调用。 每个属性都根据IsOtherSelected属性的当前值(该属性本身已绑定到ComboBox最后一项的IsSelected属性)由绑定系统自动更新,以更新Name属性。
  • 请注意,实际上并不需要响应IsOtherSelected属性中的更改。 我们知道该属性仅在SelectedValue属性发生更改时才会更改,并且对该属性所做的更改将已经更新Name属性值。
  • 原始代码正在做的一件事是将TextBoxVisibility设置为Collapsed除非在ComboBox中选择了“ Other”值。 这是通过在TextBoxStyle设置DataTrigger来实现的。 默认的VisibilityVisible ,只要不触发触发器,它就保持这种状态。 但是,如果触发该事件,即IsOtherSelected属性变为false ,则将应用DataTriggerSetter ,根据DataTriggerVisibility值设置为Collapsed 如果并且不再满足该触发器,则WPF会将属性值设置回其先前的值(即Visible )。

恕我直言,值得花时间来理解这种方法。 我了解到(最近经历了这个过程,实际上我自己一直在经历),了解WPF基于XAML /绑定的哲学是艰巨的。 在WPF中,通常有许多不同的处理方式,而且并不总是最好的方式。 同时,更难检测和理解XAML中的错误(提示:在调试器的“输出”窗口中检查程序的运行时输出)。 但是绑定系统可以处理基于GUI的程序中出现的许多演示场景,并且在许多情况下,给定程序中的所有场景都可以。 从长远来看,使用API​​会容易得多,即使一开始就更加困难。 :)


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

不幸的是,我无法弄清楚如何在XAML中进行配置(我是否提到过自己还在学习?:)),但是背后的代码并不复杂。 如果有一种方法可以在XAML中进行设置,则外观将非常相似:用MultiBinding对象代替Binding ,为其设置了Converter属性,并且将多个Binding对象添加为MultiBinding对象,如下所示:孩子们。 实际上,这通常确实可以正常工作; 我认为关于尝试绑定到DataContext对象而不是我还没有想到的FrameworkElement子类只是一些事情。

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

XAML:

<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>

C#:

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实现。 在另一个示例中, Person对象作为绑定属性的存在(即使从逻辑上讲,它是目标)。 但是有了MultiBinding ,目标确实确实需要成为目标。 由于绑定将在后台代码中指定,因此XAML没有声明任何绑定(可见性触发器除外),因此Person对象不需要可以将XAML元素绑定到的那些单独的属性。
  • 这里有一个新类ComboAndTextToTextConverter 该类将接受多个输入并将它们转换为单个输出。 您可以看到阅读了将三个值作为输入的代码: bool和两个string值(分别为ComboBox.SelectedValueTextBox.Text属性值)。 该代码对类型进行一些最小验证,然后根据需要映射值。
  • 窗口的代码后面是创建和设置MultiBinding位置。 我有一个简单的帮助程序方法,它采用目标对象和属性,以及可变数量的源对象和属性。 它只是循环遍历源代码,将它们添加到MultiBinding对象,然后在目标对象的属性上设置该MultiBinding对象。
  • 由于Person对象不再具有helper属性,包括IsOtherSelected属性,因此我继续命名了最后一个ComboBox项,以便可以按名称在TextBoxVisibility属性的触发器中绑定它。
  • 我从窗口的资源中使用了转换器,但是您可以轻松地将其放入资源中而只是创建一个新实例,即new ComboAndTextToTextConverter() 这里大多是我的选择是我早先尝试获取的神器MultiBinding通过XAML配置(其中转换器需要被声明为一个资源)。


很抱歉,冗长的答案。 我想尝试用合理的细节来解释一切,即我想自己弄清楚这些东西时希望的那种细节。 :)希望对您有所帮助!

如果我理解正确,则希望将文本框文本绑定到组合框中选择的项目的值,并且如果选择组合框项目“其他”,则文本框中的文本将设置为空。 这样实现这一目标的一种方法。

        <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>

或者如果仅在组合框中选择“其他”时才希望启用文本框,则可以...

        <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>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM