[英]WPF changing other controls property according to ComboBox selection with DataTemplate
[英]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");
}
}
}
谢谢,拉姆
我不确定是否可以改进此方法,但是异常明显与此行相关: BindingOperations.SetBinding(txtTest, TextBox.TextProperty, bin);
更改ComboBox的Text时,SelectedValue设置为null,并且再次调用cmbTest_SelectionChanged
并引发异常。
建议:
不要手动更改组合框的文本,而要使用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:
这样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);
即,当您清除对ComboBox
的Text
属性的绑定时,绑定系统会将属性值重置为null
。 这将导致递归调用事件处理程序,但是这次SelectedValue
属性为null,并且取消引用会导致NullReferenceException
。
您可以通过在尝试取消引用之前检查属性值是否为null
来解决此问题。 但这虽然可以避免异常,但不会改变这样的事实,即动态更改绑定会以各种不希望的方式动态更改各种绑定值。
我认为,您应该尝试与绑定系统本身更紧密地合作,以产生所需的结果,而不是尝试使用当前的工作方法。
一种方法是遵循我评论中的建议:
“将
ComboBox
和TextBox
每一个简单地绑定到两个单独的属性,然后完全在代码后方而不是通过其他绑定逻辑来解决“其他”方面是否合适?”
实际上,这实际上是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;
}
}
笔记:
Name
)绑定到了TextBlock
,这样就很容易在UI中看到更新。 您可能需要也可能不需要在实际代码中执行类似的操作。 DependencyObject
子类,该子类充当绑定的实际目标(即DataContext
值)。 您可以设置程序,使其实际上是代码隐藏逻辑的数据对象,或者(如在MVVM模式中一样)在中间处理实体数据对象的绑定任务。 重要的是,它是一个DependencyObject
,因此可以直接在XAML中的绑定语法中使用(实现INotifyPropertyChanged
应该起作用)。 DataContext
,而是在XAML中在此处设置它。 恕我直言,如果可以在XAML中处理某些内容而不是在背后的代码中进行处理,则最好这样做。 这使XAML编辑器和编译器可以更多地了解实现细节,并以Intellisense或适当的编译时错误的形式提供更好的反馈。 Name
属性的部分是PropertyChangedCallback
方法, OnComboBoxTextChanged()
和OnTextBoxTextChanged()
。 当注册这些属性的DependencyProperty
值时,将为这些属性提供这些回调方法,并在属性值更改时被调用。 每个属性都根据IsOtherSelected
属性的当前值(该属性本身已绑定到ComboBox
最后一项的IsSelected
属性)由绑定系统自动更新,以更新Name
属性。 IsOtherSelected
属性中的更改。 我们知道该属性仅在SelectedValue
属性发生更改时才会更改,并且对该属性所做的更改将已经更新Name
属性值。 TextBox
的Visibility
设置为Collapsed
除非在ComboBox
中选择了“ Other”值。 这是通过在TextBox
的Style
设置DataTrigger
来实现的。 默认的Visibility
为Visible
,只要不触发触发器,它就保持这种状态。 但是,如果触发该事件,即IsOtherSelected
属性变为false
,则将应用DataTrigger
的Setter
,根据DataTrigger
将Visibility
值设置为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.SelectedValue
和TextBox.Text
属性值)。 该代码对类型进行一些最小验证,然后根据需要映射值。 MultiBinding
位置。 我有一个简单的帮助程序方法,它采用目标对象和属性,以及可变数量的源对象和属性。 它只是循环遍历源代码,将它们添加到MultiBinding
对象,然后在目标对象的属性上设置该MultiBinding
对象。 Person
对象不再具有helper属性,包括IsOtherSelected
属性,因此我继续命名了最后一个ComboBox
项,以便可以按名称在TextBox
的Visibility
属性的触发器中绑定它。 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.