我是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");
}
}
}
谢谢, 拉姆
答案 0 :(得分:2)
我不确定如何改进这一点,但例外显然与此行有关:
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绑定到您的视图:
<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;将
null
和ComboBox
中的每一个简单地绑定到两个单独的属性并解析&#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>
注意:
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或编译时错误的形式提供更好的反馈。DataContext
属性的部分是Name
方法,PropertyChangedCallback
和OnComboBoxTextChanged()
。注册这些属性的OnTextBoxTextChanged()
值时,会为属性提供这些回调方法,并在属性值发生更改时调用。每个属性都根据DependencyProperty
属性的当前值更新Name
属性(绑定系统本身自动保持最新,它已绑定到IsOtherSelected
属性您IsSelected
)中的最后一项。ComboBox
属性中的更改。我们知道只有当IsOtherSelected
属性发生更改时,属性才会更改,并且对该属性的更改将更新SelectedValue
属性值。Name
的{{1}}设置为Visibility
,除非&#34;其他&#34;在TextBox
中选择了值。这是通过在Collapsed
ComboBox
中为DataTrigger
设置Style
来实现的。默认TextBox
为Visibility
,只要触发器未被触发,它就会保持这种状态。但是如果它被触发,即Visible
属性变为IsOtherSelected
,false
中的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
值(分别为bool
和string
属性值)。代码对类型进行了一些最小的验证,然后根据需要映射值。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>