![](/img/trans.png)
[英]WPF binding to ComboBox SelectedItem when reference not in ItemsSource
[英]WPF ComboBox: Set SelectedItem to item not in ItemsSource -> Binding oddity
我想实现以下目标:我想要一个显示可用 COM 端口的 ComboBox。 在启动(并单击“刷新”按钮)时,我想获取可用的 COM 端口并将选择设置为最后选择的值(从应用程序设置)。
如果设置中的值(最后一个 com 端口)不在值列表(可用的 COM 端口)中,则会发生以下情况:
尽管 ComboBox 不显示任何内容(知道新的 SelectedItem 不在 ItemsSource 中已经“足够聪明”),但 ViewModel 已更新为“无效值”。 我实际上希望 Binding 具有与 ComboBox 显示相同的值。
用于演示目的的代码:
主窗口.xaml:
<Window x:Class="DemoComboBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:DemoComboBinding">
<Window.Resources>
<local:DemoViewModel x:Key="vm" />
</Window.Resources>
<StackPanel Orientation="Vertical">
<ComboBox SelectedItem="{Binding Source={StaticResource vm}, Path=Selected}" x:Name="combo"
ItemsSource="{Binding Source={StaticResource vm}, Path=Source}"/>
<Button Click="Button_Click">Set different</Button> <!-- would be refresh button -->
<Label Content="{Binding Source={StaticResource vm}, Path=Selected}"/> <!-- shows the value from the view model -->
</StackPanel>
</Window>
主窗口.xaml.cs:
// usings removed
namespace DemoComboBinding
{
public partial class MainWindow : Window
{
//...
private void Button_Click(object sender, RoutedEventArgs e)
{
combo.SelectedItem = "COM4"; // would be setting from Properties
}
}
}
视图模型:
namespace DemoComboBinding
{
class DemoViewModel : INotifyPropertyChanged
{
string selected;
string[] source = { "COM1", "COM2", "COM3" };
public string[] Source
{
get { return source; }
set { source = value; }
}
public string Selected
{
get { return selected; }
set {
if(selected != value)
{
selected = value;
OnpropertyChanged("Selected");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
void OnpropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
#endregion
}
}
我最初想出的一个解决方案是检查 Selected setter 中要设置的值是否在可用 COM 端口列表中(如果不是,则设置为空字符串并发送 OPC)。
我想知道:为什么会这样? 还有其他我没有看到的解决方案吗?
简而言之,您不能将SelectedItem
设置为ItemsSource
没有的值。 AFAIK,这是所有Selector
子孙的默认行为,这非常明显:设置SelectedItem
不仅是数据更改,还应该导致一些视觉上的后果,例如生成项目容器和重新绘制项目(所有这些操作操纵ItemsSource
)。 您在这里可以做的最好的事情是这样的代码:
public DemoViewModel()
{
selected = Source.FirstOrDefault(s => s == yourValueFromSettings);
}
另一种选择是让用户在输入任意值ComboBox
通过使编辑。
我知道这对您有所帮助有点晚,但我希望至少对您有所帮助。 对不起,如果有错别字,我必须在记事本中输入:
ComboBoxAdaptor.cs:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace Adaptors
{
[ContentProperty("ComboBox")]
public class ComboBoxAdaptor : ContentControl
{
#region Protected Properties
protected bool IsChangingSelection
{ get; set; }
protected ICollectionView CollectionView
{ get; set; }
#endregion
#region Dependency Properties
public static readonly DependencyProperty ComboBoxProperty =
DependencyProperty.Register("ComboBox", typeof(ComboBox), typeof(ComboBoxAdaptor),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ComboBox_Changed)));
private static void ComboBox_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var theComboBoxAdaptor = (ComboBoxAdaptor)d;
theComboBoxAdaptor.ComboBox.SelectionChanged += theComboBoxAdaptor.ComboBox_SelectionChanged;
}
public ComboBox ComboBox
{
get { return (ComboBox)GetValue(ComboBoxProperty); }
set { SetValue(ComboBoxProperty, value); }
}
public static readonly DependencyProperty NullItemProperty =
DependencyProperty.Register("NullItem", typeof(object), typeof(ComboBoxAdaptor),
new PropertyMetadata("(None)"));
public object NullItem
{
get { return GetValue(NullItemProperty); }
set { SetValue(NullItemProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ComboBoxAdaptor),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ItemsSource_Changed)));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboBoxAdaptor),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(SelectedItem_Changed)));
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty AllowNullProperty =
DependencyProperty.Register("AllowNull", typeof(bool), typeof(ComboBoxAdaptor),
new PropertyMetadata(true, AllowNull_Changed));
public bool AllowNull
{
get { return (bool)GetValue(AllowNullProperty); }
set { SetValue(AllowNullProperty, value); }
}
#endregion
#region static PropertyChangedCallbacks
static void ItemsSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
adapter.Adapt();
}
static void AllowNull_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
adapter.Adapt();
}
static void SelectedItem_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
if (adapter.ItemsSource != null)
{
//If SelectedItem is changing from the Source (which we can tell by checking if the
//ComboBox.SelectedItem is already set to the new value), trigger Adapt() so that we
//throw out any items that are not in ItemsSource.
object adapterValue = (e.NewValue ?? adapter.NullItem);
object comboboxValue = (adapter.ComboBox.SelectedItem ?? adapter.NullItem);
if (!object.Equals(adapterValue, comboboxValue))
{
adapter.Adapt();
adapter.ComboBox.SelectedItem = e.NewValue;
}
//If the NewValue is not in the CollectionView (and therefore not in the ComboBox)
//trigger an Adapt so that it will be added.
else if (e.NewValue != null && !adapter.CollectionView.Contains(e.NewValue))
{
adapter.Adapt();
}
}
}
#endregion
#region Misc Callbacks
void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ComboBox.SelectedItem == NullItem)
{
if (!IsChangingSelection)
{
IsChangingSelection = true;
try
{
int selectedIndex = ComboBox.SelectedIndex;
ComboBox.SelectedItem = null;
ComboBox.SelectedIndex = -1;
ComboBox.SelectedIndex = selectedIndex;
}
finally
{
IsChangingSelection = false;
}
}
}
object newVal = (ComboBox.SelectedItem == null ? null : ComboBox.SelectedItem);
if (!object.Equals(SelectedItem, newVal))
{
SelectedItem = newVal;
}
}
void CollectionView_CurrentChanged(object sender, EventArgs e)
{
if (AllowNull && (ComboBox != null) && (((ICollectionView)sender).CurrentItem == null) && (ComboBox.Items.Count > 0))
{
ComboBox.SelectedIndex = 0;
}
}
#endregion
#region Methods
protected void Adapt()
{
if (CollectionView != null)
{
CollectionView.CurrentChanged -= CollectionView_CurrentChanged;
CollectionView = null;
}
if (ComboBox != null && ItemsSource != null)
{
CompositeCollection comp = new CompositeCollection();
//If AllowNull == true, add a "NullItem" as the first item in the ComboBox.
if (AllowNull)
{
comp.Add(NullItem);
}
//Now Add the ItemsSource.
comp.Add(new CollectionContainer { Collection = ItemsSource });
//Lastly, If Selected item is not null and does not already exist in the ItemsSource,
//Add it as the last item in the ComboBox
if (SelectedItem != null)
{
List<object> items = ItemsSource.Cast<object>().ToList();
if (!items.Contains(SelectedItem))
{
comp.Add(SelectedItem);
}
}
CollectionView = CollectionViewSource.GetDefaultView(comp);
if (CollectionView != null)
{
CollectionView.CurrentChanged += CollectionView_CurrentChanged;
}
ComboBox.ItemsSource = comp;
}
}
#endregion
}
}
如何在Xaml中使用它
<adaptor:ComboBoxAdaptor
NullItem="Please Select an Item.."
ItemsSource="{Binding MyItemsSource}"
SelectedItem="{Binding MySelectedItem}">
<ComboBox Width="100" />
</adaptor:ComboBoxAdaptor>
一些注意事项
如果SelectedItem
更改为不在ComboBox
的值,它将被添加到ComboBox
(而不是ItemsSource)。 下次通过Binding
更改SelectedItem
,不在ItemsSource
所有项目将从ComboBox
删除。
另外, ComboBoxAdaptor
允许您将Null项插入ComboBox
。 这是一项可选功能,您可以通过在xaml中设置AllowNull="False"
来关闭它。
您可以通过创建一个单单元格的 Grid,然后将 ComboBox 放在网格中,并将 TextBlock 放在 ComboBox 的顶部来实现类似的效果。 TextBlock 的可见性只需要通过绑定到 ComboBox 的 Text.IsEmpty 属性来控制。
您可能需要调整文本框的边距、对齐方式、大小和其他属性以使其看起来不错。
<Grid>
<ComboBox Name="MyComboBox"
ItemsSource="{Binding Options}"
SelectedIndex="{Binding SelectedIndex}" />
<TextBlock Text="{Binding EmptySelectionPromptText}"
Margin="4 3 0 0"
Visibility="{Binding ElementName=MyComboBox, Path=Text.IsEmpty, Converter={StaticResource BoolToVis}}">
</TextBlock>
</Grid>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.