wpf 可编辑的 ComboBox 绑定和更新源触发器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30577176/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Editable ComboBox binding and update source trigger
提问by Sinatr
Requirement
要求
I want to have ComboBoxwhere user may enter some text or choose text from drop-down list. Binding source should be updated when user press Enterafter typing or when item is simply selected from drop-down list (best View behavior in my case).
我希望ComboBox用户可以在何处输入一些文本或从下拉列表中选择文本。当用户Enter在键入后按下或从下拉列表中简单地选择项目时(在我的情况下是最佳视图行为),应该更新绑定源。
Problem
问题
- when
UpdateSourceTrigger=PropertyChange(default) is set, then source update will trigger after every character, which is not good, because property setter call is expensive; - when
UpdateSourceTrigger=LostFocusis set, then selecting item from drop-down list will require one more action to actually lose focus, which is not very user-friendly (required additional click after click to select item).
- 当
UpdateSourceTrigger=PropertyChange(默认)被设置时,那么在每个字符后都会触发源更新,这不好,因为属性设置器调用很昂贵; - 当
UpdateSourceTrigger=LostFocus设置时,那么从下拉列表中选择项目将需要多一个动作才能真正失去焦点,这不是很人性化(需要在单击后额外单击以选择项目)。
I tried to use UpdateSourceTrigger=Explicit, but it doesn't go well:
我尝试使用UpdateSourceTrigger=Explicit,但效果不佳:
<ComboBox IsEditable="True" VerticalAlignment="Top" ItemsSource="{Binding List}"
Text="{Binding Text, UpdateSourceTrigger=LostFocus}"
SelectionChanged="ComboBox_SelectionChanged"
PreviewKeyDown="ComboBox_PreviewKeyDown" LostFocus="ComboBox_LostFocus"/>
public partial class MainWindow : Window
{
private string _text = "Test";
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
MessageBox.Show(value);
}
}
}
public string[] List
{
get { return new[] { "Test", "AnotherTest" }; }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
}
private void ComboBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
}
private void ComboBox_LostFocus(object sender, RoutedEventArgs e)
{
((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
}
}
This code has 2 issues:
这段代码有两个问题:
- when item is selected from drop-down menu, then source is updated with previouslyselected value, why?
- when user start typing something and then click drop-down button to choose something from the list - source is updated again (due to focus lost?), how to avoid that?
- 当从下拉菜单中选择项目时,源会更新为先前选择的值,为什么?
- 当用户开始输入内容然后单击下拉按钮从列表中选择内容时 - 源再次更新(由于焦点丢失?),如何避免这种情况?
I am a bit afraid to fall under XY problemthat's why I posted original requirement (perhaps I went wrong direction?) instead of asking to help me to fix one of above problems.
我有点害怕陷入XY 问题,这就是为什么我发布了原始要求(也许我走错了方向?)而不是要求帮助我解决上述问题之一。
回答by Xavier
Your approach of updating the source in response to specific events is on the right track, but there is more you have to take into account with the way ComboBoxupdates things. Also, you probably want to leave UpdateSourceTriggerset to LostFocusso that you do not have as many update cases to deal with.
您更新源以响应特定事件的方法是正确的,但是在ComboBox更新事物的方式时,您还需要考虑更多。此外,您可能希望保留UpdateSourceTrigger设置为,LostFocus以便您没有那么多的更新案例要处理。
You should also consider moving the code to a reusable attached property so you can apply it to combo boxes elsewhere in the future. It so happens that I have created such a property in the past.
您还应该考虑将代码移动到可重用的附加属性,以便将来可以将其应用于其他地方的组合框。碰巧我过去已经创建了这样的属性。
/// <summary>
/// Attached properties for use with combo boxes
/// </summary>
public static class ComboBoxBehaviors
{
private static bool sInSelectionChange;
/// <summary>
/// Whether the combo box should commit changes to its Text property when the Enter key is pressed
/// </summary>
public static readonly DependencyProperty CommitOnEnterProperty = DependencyProperty.RegisterAttached("CommitOnEnter", typeof(bool), typeof(ComboBoxBehaviors),
new PropertyMetadata(false, OnCommitOnEnterChanged));
/// <summary>
/// Returns the value of the CommitOnEnter property for the specified ComboBox
/// </summary>
public static bool GetCommitOnEnter(ComboBox control)
{
return (bool)control.GetValue(CommitOnEnterProperty);
}
/// <summary>
/// Sets the value of the CommitOnEnterProperty for the specified ComboBox
/// </summary>
public static void SetCommitOnEnter(ComboBox control, bool value)
{
control.SetValue(CommitOnEnterProperty, value);
}
/// <summary>
/// Called when the value of the CommitOnEnter property changes for a given ComboBox
/// </summary>
private static void OnCommitOnEnterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ComboBox control = sender as ComboBox;
if (control != null)
{
if ((bool)e.OldValue)
{
control.KeyUp -= ComboBox_KeyUp;
control.SelectionChanged -= ComboBox_SelectionChanged;
}
if ((bool)e.NewValue)
{
control.KeyUp += ComboBox_KeyUp;
control.SelectionChanged += ComboBox_SelectionChanged;
}
}
}
/// <summary>
/// Handler for the KeyUp event attached to a ComboBox that has CommitOnEnter set to true
/// </summary>
private static void ComboBox_KeyUp(object sender, KeyEventArgs e)
{
ComboBox control = sender as ComboBox;
if (control != null && e.Key == Key.Enter)
{
BindingExpression expression = control.GetBindingExpression(ComboBox.TextProperty);
if (expression != null)
{
expression.UpdateSource();
}
e.Handled = true;
}
}
/// <summary>
/// Handler for the SelectionChanged event attached to a ComboBox that has CommitOnEnter set to true
/// </summary>
private static void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!sInSelectionChange)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(ComboBox.TextProperty, typeof(ComboBox));
descriptor.AddValueChanged(sender, ComboBox_TextChanged);
sInSelectionChange = true;
}
}
/// <summary>
/// Handler for the Text property changing as a result of selection changing in a ComboBox that has CommitOnEnter set to true
/// </summary>
private static void ComboBox_TextChanged(object sender, EventArgs e)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(ComboBox.TextProperty, typeof(ComboBox));
descriptor.RemoveValueChanged(sender, ComboBox_TextChanged);
ComboBox control = sender as ComboBox;
if (control != null && sInSelectionChange)
{
sInSelectionChange = false;
if (control.IsDropDownOpen)
{
BindingExpression expression = control.GetBindingExpression(ComboBox.TextProperty);
if (expression != null)
{
expression.UpdateSource();
}
}
}
}
}
Here is an example of setting the property in xaml:
以下是在 xaml 中设置属性的示例:
<ComboBox IsEditable="True" ItemsSource="{Binding Items}" Text="{Binding SelectedItem, UpdateSourceTrigger=LostFocus}" local:ComboBoxBehaviors.CommitOnEnter="true" />
I think this will give you the behavior you are looking for. Feel free to use it as-is or modify it to your liking.
我认为这会给你你正在寻找的行为。随意按原样使用它或根据自己的喜好修改它。
There is one issue with the behavior implementation where if you start to type an existing value (and don't press enter), then choose that same value from the dropdown, the source does not get updated in that case until you press enter, change focus, or choose a different value. I am sure that could be worked out, but it was not enough of an issue for me to spend time on it since it is not a normal workflow.
行为实现存在一个问题,如果您开始键入现有值(并且不按 Enter),然后从下拉列表中选择相同的值,在这种情况下,源不会更新,直到您按 Enter,更改焦点,或选择不同的值。我确信这可以解决,但对我来说花时间解决它还不够,因为它不是正常的工作流程。
回答by jdnew18
I would recommend keeping UpdateSourceTrigger=PropertyChanged, and also putting a delay on the combobox to help mitigate your expensive setter/update problem. The delay will cause the PropertyChangedevent to wait the amount of milliseconds you specify before firing.
我建议保留UpdateSourceTrigger=PropertyChanged,并延迟组合框以帮助减轻昂贵的设置器/更新问题。延迟将导致PropertyChanged事件在触发之前等待您指定的毫秒数。
More on Delay here: http://www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/
更多关于延迟在这里:http: //www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/
Hopefully somebody will come up with a better solution for you, but this should at least get you moving along for now.
希望有人会为您提出更好的解决方案,但这至少应该让您现在继续前进。
回答by Denise Skidmore
I had a similar issue, I handled it in the .cs code. It isn't the XAML way, but it gets it done. First I broke the binding, then manually propagated the value both ways.
我有一个类似的问题,我在 .cs 代码中处理了它。这不是 XAML 方式,但它可以完成。首先,我打破了绑定,然后双向手动传播该值。
<ComboBox x:Name="Combo_MyValue"
ItemsSource="{Binding Source={StaticResource ListData}, XPath=MyContextType/MyValueType}"
DisplayMemberPath="@Description"
SelectedValuePath="@Value"
IsEditable="True"
Loaded="Combo_MyValue_Loaded"
SelectionChanged = "Combo_MyValue_SelectionChanged"
LostFocus="Combo_MyValue_LostFocus"
/>
private void Combo_MyValue_Loaded(object sender, RoutedEventArgs e)
{
if (DataContext != null)
{
Combo_MyValue.SelectedValue = ((MyContextType)DataContext).MyValue;
}
}
private void Combo_MyValue_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if( e.AddedItems.Count == 0)
{
// this is a custom value, we'll set it in the lost focus event
return;
}
// this is a picklist value, get the value from itemsource
XmlElement selectedItem = (XmlElement)e.AddedItems[0];
string selectedValue = selectedItem.GetAttribute("Value");
((PumpParameters)DataContext).MyValue = selectedValue;
}
private void Combo_MyValue_LostFocus(object sender, RoutedEventArgs e)
{
if( Combo_MyValue.IsDropDownOpen || Combo_MyValue.SelectedIndex != -1)
{
// not a custom value
return;
}
// custom value
((MyContextType)DataContext).MyValue = Combo_MyValue.Text;
}

