2 个 ObservableCollections 1 个组合框 WPF
2 ObservableCollections 1 Combobox WPF
我有一个 WPF 应用程序,它与另一台计算机建立连接。在我的应用程序中,我有一个组合框,用户可以在其中输入计算机的主机名,然后连接到这台计算机。现在一旦建立连接,用户输入的主机名就会保存到绑定到组合框的 Observable Collection 中,因此下次他想连接到同一主机时,他可以直接从组合框中选择它盒子.
我实现了收藏列表。这是一个单独的可观察对象 collection,我也想将其绑定到同一个组合框,因此用户可以选择收藏项或历史记录项。
在组合框的下拉列表中,我想要 2 个带有 Header 的分组,如下所示:
[Favorites]
My Favourite Host | myfavhost.com
My 2nd Fav | my2ndfav.com
Secretly My Fav | secretlymyfav.com
[History]
hostioncevisited.com
whyamihere.com
thanksforhelping.com
现在我真的不知道该怎么做。有没有办法将多个项目源绑定到组合框,或者我是否必须在将它们绑定到组合框之前合并两个可观察的 collection?
这些是我观察到的 collections
public ObservableCollection<string> HistoryItems { get; set; } = new ObservableCollection<string>();
public static ObservableCollection<FavoriteItem> FavoriteItems { get; set; } = new ObservableCollection<FavoriteItem>();
这是我最喜欢的项目Class
public class FavoriteItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string hostName;
private string description;
public FavoriteItem(){}
public FavoriteItem(string _hostName, string _description)
{
hostName = _hostName;
description = _description;
}
public string Hostname
{
get { return hostName; }
set
{
hostName = value;
OnPropertyChanged("Hostname");
}
}
public string Description
{
get { return description; }
set
{
description = value;
OnPropertyChanged("Description");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public override string ToString()
{
return string.Format("{0} | {1}", description, hostName);
}
}
这是组合框的XAML
XAML
<ComboBox Name="cbHostName" Style="{StaticResource ComboBoxLarge}" Text="{Binding HostName}" ItemsSource="{Binding HistoryItems}"
MinWidth="300" MaxWidth="300" IsEditable="True" Margin="0,0,15,0" VerticalAlignment="Center" materialDesign:HintAssist.Hint="Computer, IP or HostProfileName"/>
不可以,您不能将多个集合绑定到 ItemsSource,您必须合并它们
您可以使用 CompositeCollection 将多个集合绑定到同一来源。
缺点是我认为在这种情况下分组是不可能的(至少不容易)。
备选方案 将只有一个列表,对象实现相同的接口,用一些 属性 来区分项目的类型,例如:
public interface IHost : INotifyPropertyChanged
{
string HostType { get; }
string Hostname { get; set; }
string DisplayText { get; set; }
}
public class HistoryItem : IHost
{
public event PropertyChangedEventHandler PropertyChanged;
public string HostType => "History";
public string Hostname { get; set; }
public string DisplayText => Hostname;
}
public class FavoriteItem : IHost
{
public event PropertyChangedEventHandler PropertyChanged;
public string HostType => "Favorites";
public string Hostname { get; set; }
public string Description { get; set; }
public string DisplayText => Description == null ? Hostname : $"{Description} | {Hostname}";
//other properties....
}
因为我发现直接使用 ObservableCollection
很烦人,所以我倾向于使用它的包装器(代码在底部)。它处理一些常见问题,例如可能的内存泄漏和在添加多个项目时引发不必要的 CollectionChanged
事件。它还提供了对代码隐藏中的分组、排序、过滤、当前项目和 CurrentChanged
& CurrentChanging
事件的轻松访问。
在视图模型中:
public ViewableCollection<IHost> MyItems { get; set; }
正在初始化集合:
this.MyItems = new ViewableCollection<IHost>();
// decide how your items will be sorted (important: first sort groups, then items in groups)
this.MyItems.View.SortDescriptions.Add(new SortDescription("HostType", ListSortDirection.Ascending)); // sorting of groups
this.MyItems.View.SortDescriptions.Add(new SortDescription("Hostname", ListSortDirection.Ascending)); // sorting of items
PropertyGroupDescription groupDescription = new PropertyGroupDescription("HostType");
this.MyItems.View.GroupDescriptions.Add(groupDescription);
this.MyItems.View.CurrentChanged += MyItems_CurrentChanged;
this.MyItems.AddRange(new IHost[] {
new HistoryItem { Hostname = "ccc" },
new HistoryItem { Hostname = "aaa" },
new HistoryItem { Hostname = "xxx" },
new FavoriteItem { Hostname = "vvv" },
new FavoriteItem { Hostname = "bbb" },
new FavoriteItem { Hostname = "ttt" } });
选择项目时将执行此代码:
private void MyItems_CurrentChanged(object sender, EventArgs e)
{
Console.WriteLine("Selected item: " + this.MyItems.CurrentItem?.Hostname);
}
这里是ComboBox
的xaml分组(使用ViewableCollection
,需要将ItemsSource
绑定到MyItems.View
而不是直接绑定到MyItems
):
<ComboBox ItemsSource="{Binding MyItems.View, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="DisplayText">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Items.CurrentItem.HostType, StringFormat=[{0}]}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
</ComboBox>
结果:
[DoNotNotify]
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ViewableCollection(IEnumerable<T> items)
: base(items) { }
public ViewableCollection()
: base() { }
[XmlIgnore]
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
_View.CurrentChanged += new EventHandler(InnerView_CurrentChanged);
}
return _View;
}
}
[XmlIgnore]
public T CurrentItem
{
get
{
return (T)this.View.CurrentItem;
}
set
{
this.View.MoveCurrentTo(value);
}
}
private void InnerView_CurrentChanged(object sender, EventArgs e)
{
this.OnPropertyChanged(new PropertyChangedEventArgs("CurrentItem"));
}
public void AddRange(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ReplaceItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
this.Items.Clear();
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Remove(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ClearAll()
{
IList old = this.Items.ToList();
base.Items.Clear();
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void CallCollectionChaged()
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator List<T>(ViewableCollection<T> o)
{
return o == null ? default(List<T>) : o.ToList();
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator ViewableCollection<T>(List<T> o)
{
return o == default(List<T>) || o == null ? new ViewableCollection<T>() : new ViewableCollection<T>(o);
}
}
以上代码是一个工作示例。我正在使用 nuget 包 PropertyChanged2.Fody 来注入 PropertyChanged
通知。
我有一个 WPF 应用程序,它与另一台计算机建立连接。在我的应用程序中,我有一个组合框,用户可以在其中输入计算机的主机名,然后连接到这台计算机。现在一旦建立连接,用户输入的主机名就会保存到绑定到组合框的 Observable Collection 中,因此下次他想连接到同一主机时,他可以直接从组合框中选择它盒子.
我实现了收藏列表。这是一个单独的可观察对象 collection,我也想将其绑定到同一个组合框,因此用户可以选择收藏项或历史记录项。
在组合框的下拉列表中,我想要 2 个带有 Header 的分组,如下所示:
[Favorites]
My Favourite Host | myfavhost.com
My 2nd Fav | my2ndfav.com
Secretly My Fav | secretlymyfav.com
[History]
hostioncevisited.com
whyamihere.com
thanksforhelping.com
现在我真的不知道该怎么做。有没有办法将多个项目源绑定到组合框,或者我是否必须在将它们绑定到组合框之前合并两个可观察的 collection?
这些是我观察到的 collections
public ObservableCollection<string> HistoryItems { get; set; } = new ObservableCollection<string>();
public static ObservableCollection<FavoriteItem> FavoriteItems { get; set; } = new ObservableCollection<FavoriteItem>();
这是我最喜欢的项目Class
public class FavoriteItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string hostName;
private string description;
public FavoriteItem(){}
public FavoriteItem(string _hostName, string _description)
{
hostName = _hostName;
description = _description;
}
public string Hostname
{
get { return hostName; }
set
{
hostName = value;
OnPropertyChanged("Hostname");
}
}
public string Description
{
get { return description; }
set
{
description = value;
OnPropertyChanged("Description");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public override string ToString()
{
return string.Format("{0} | {1}", description, hostName);
}
}
这是组合框的XAML
XAML
<ComboBox Name="cbHostName" Style="{StaticResource ComboBoxLarge}" Text="{Binding HostName}" ItemsSource="{Binding HistoryItems}"
MinWidth="300" MaxWidth="300" IsEditable="True" Margin="0,0,15,0" VerticalAlignment="Center" materialDesign:HintAssist.Hint="Computer, IP or HostProfileName"/>
不可以,您不能将多个集合绑定到 ItemsSource,您必须合并它们
您可以使用 CompositeCollection 将多个集合绑定到同一来源。
缺点是我认为在这种情况下分组是不可能的(至少不容易)。
备选方案 将只有一个列表,对象实现相同的接口,用一些 属性 来区分项目的类型,例如:
public interface IHost : INotifyPropertyChanged
{
string HostType { get; }
string Hostname { get; set; }
string DisplayText { get; set; }
}
public class HistoryItem : IHost
{
public event PropertyChangedEventHandler PropertyChanged;
public string HostType => "History";
public string Hostname { get; set; }
public string DisplayText => Hostname;
}
public class FavoriteItem : IHost
{
public event PropertyChangedEventHandler PropertyChanged;
public string HostType => "Favorites";
public string Hostname { get; set; }
public string Description { get; set; }
public string DisplayText => Description == null ? Hostname : $"{Description} | {Hostname}";
//other properties....
}
因为我发现直接使用 ObservableCollection
很烦人,所以我倾向于使用它的包装器(代码在底部)。它处理一些常见问题,例如可能的内存泄漏和在添加多个项目时引发不必要的 CollectionChanged
事件。它还提供了对代码隐藏中的分组、排序、过滤、当前项目和 CurrentChanged
& CurrentChanging
事件的轻松访问。
在视图模型中:
public ViewableCollection<IHost> MyItems { get; set; }
正在初始化集合:
this.MyItems = new ViewableCollection<IHost>();
// decide how your items will be sorted (important: first sort groups, then items in groups)
this.MyItems.View.SortDescriptions.Add(new SortDescription("HostType", ListSortDirection.Ascending)); // sorting of groups
this.MyItems.View.SortDescriptions.Add(new SortDescription("Hostname", ListSortDirection.Ascending)); // sorting of items
PropertyGroupDescription groupDescription = new PropertyGroupDescription("HostType");
this.MyItems.View.GroupDescriptions.Add(groupDescription);
this.MyItems.View.CurrentChanged += MyItems_CurrentChanged;
this.MyItems.AddRange(new IHost[] {
new HistoryItem { Hostname = "ccc" },
new HistoryItem { Hostname = "aaa" },
new HistoryItem { Hostname = "xxx" },
new FavoriteItem { Hostname = "vvv" },
new FavoriteItem { Hostname = "bbb" },
new FavoriteItem { Hostname = "ttt" } });
选择项目时将执行此代码:
private void MyItems_CurrentChanged(object sender, EventArgs e)
{
Console.WriteLine("Selected item: " + this.MyItems.CurrentItem?.Hostname);
}
这里是ComboBox
的xaml分组(使用ViewableCollection
,需要将ItemsSource
绑定到MyItems.View
而不是直接绑定到MyItems
):
<ComboBox ItemsSource="{Binding MyItems.View, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="DisplayText">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Items.CurrentItem.HostType, StringFormat=[{0}]}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
</ComboBox>
结果:
[DoNotNotify]
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ViewableCollection(IEnumerable<T> items)
: base(items) { }
public ViewableCollection()
: base() { }
[XmlIgnore]
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
_View.CurrentChanged += new EventHandler(InnerView_CurrentChanged);
}
return _View;
}
}
[XmlIgnore]
public T CurrentItem
{
get
{
return (T)this.View.CurrentItem;
}
set
{
this.View.MoveCurrentTo(value);
}
}
private void InnerView_CurrentChanged(object sender, EventArgs e)
{
this.OnPropertyChanged(new PropertyChangedEventArgs("CurrentItem"));
}
public void AddRange(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ReplaceItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
this.Items.Clear();
foreach (T item in range)
{
this.Items.Add(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveItems(IEnumerable<T> range)
{
if (range == null)
throw new ArgumentNullException("range");
foreach (T item in range)
{
this.Items.Remove(item);
}
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ClearAll()
{
IList old = this.Items.ToList();
base.Items.Clear();
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void CallCollectionChaged()
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator List<T>(ViewableCollection<T> o)
{
return o == null ? default(List<T>) : o.ToList();
}
// necessary for xml easy serialization using [XmlArray] attribute
public static implicit operator ViewableCollection<T>(List<T> o)
{
return o == default(List<T>) || o == null ? new ViewableCollection<T>() : new ViewableCollection<T>(o);
}
}
以上代码是一个工作示例。我正在使用 nuget 包 PropertyChanged2.Fody 来注入 PropertyChanged
通知。