尽管在编写 Winforms 应用程序方面有一定的经验,但 WPF 的“模糊性”在最佳实践和设计模式方面仍然让我困惑。
尽管在运行时填充我的列表,但我的列表框显示为空。
我已按照这篇有用的文章中的简单说明进行操作,但无济于事。我怀疑我缺少某种
DataBind()
方法,在该方法中我告诉列表框我已完成修改底层列表。
在我的 MainWindow.xaml 中,我有:
<ListBox ItemsSource="{Binding TopicList}" Height="177" HorizontalAlignment="Left" Margin="15,173,0,0" Name="listTopics" VerticalAlignment="Top" Width="236" Background="#0B000000">
<ListBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</HierarchicalDataTemplate>
</ListBox.ItemTemplate>
</ListBox>
在我的代码隐藏中,我有:
private void InitializeTopicList( MyDataContext context )
{
List<Topic> topicList = ( from topic in context.Topics select topic ).ToList();
foreach ( Topic topic in topicList )
{
CheckedListItem item = new CheckedListItem();
item.Name = topic.DisplayName;
item.ID = topic.ID;
TopicList.Add( item );
}
}
通过追踪,我知道其中填充了四个项目。
编辑
我已将
TopicList
更改为 ObservableCollection
。还是不行。
public ObservableCollection<CheckedListItem> TopicList;
编辑#2
我做了两项有帮助的更改:
在 .xaml 文件中:
ListBox ItemsSource="{Binding}"
在填充列表后的源代码中:
listTopics.DataContext = TopicList;
我得到了一个列表,但当我刷新这些复选框时,它不会自动更新复选框状态。我怀疑我进一步阅读会解决这个问题。
假设
TopicList
不是 ObservableCollection<T>
,因此当您添加项目时,不会触发 INotifyCollection
更改以告诉绑定引擎更新值。
将您的
TopicList
更改为 ObservableCollection<T>
这将解决当前问题。您还可以提前填充 List<T>
,然后绑定将通过 OneWay 进行;然而 ObservableCollection<T>
是一种更稳健的方法。
编辑:
您的
TopicList
需要是属性而不是成员变量;绑定需要属性。它确实不需要需要是DependencyProperty
。
编辑2:
修改您的
ItemTemplate
,因为它不需要是 HierarchicalDataTemplate
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
使用
ObservableCollection<Topic>
代替 List<Topic>
编辑
它实现了 INotifyCollectionChanged 接口,让 WPF 知道您何时添加/删除/修改项目
编辑2
既然你在代码中设置了
TopicList
,它应该是一个依赖属性,而不是一个公共字段
public ObservableCollection<CheckedListItem> TopicList {
get { return (ObservableCollection<CheckedListItem>)GetValue(TopicListProperty); }
set { SetValue(TopicListProperty, value); }
}
public static readonly DependencyProperty TopicListProperty =
DependencyProperty.Register("TopicList", typeof(ObservableCollection<CheckedListItem>), typeof(MainWindow), new UIPropertyMetadata(null));
编辑3
查看项目变化
INotifyPropertyChanged
中实现CheckedListItem
接口(每个setter应该调用PropertyChanged(this, new PropertyChangedEventArgs(<property name as string>))
事件)CheckedListItem
派生 DependencyObject
,并将 Name
、ID
、IsChecked
转换为依赖属性topicList[0] = new CheckedListItem() { Name = ..., ID = ... }
)首先,您不需要 HeirarchicalDataTemplate。只需亚伦给出的常规 DataTemplate 就足够了。 然后,您需要在类的构造函数内的某个位置实例化 TopicList ObservableCollection。这甚至在向 ObservableCollection 添加数据之前就使它处于活动状态并且绑定系统知道该集合。然后,当您添加每个主题/CheckedListItem 时,它将自动显示在 UI 中。
TopicList = new ObservableCollection<CheckedListItem>(); //This should happen only once
private void InitializeTopicList( MyDataContext context )
{
TopicList.Clear();
foreach ( Topic topic in topicList )
{
CheckedListItem item = new CheckedListItem();
item.Name = topic.DisplayName;
item.ID = topic.ID;
TopicList.Add( item );
}
}
其他人已经提出了有用的建议(使用可观察的集合来获取列表更改通知,使集合成为属性而不是字段)。这是他们没有的两个:
1) 每当您遇到数据绑定问题时,请查看“输出”窗口以确保没有收到任何绑定错误。如果不这样做,您可能会花费大量时间尝试解决错误的问题。
2) 了解角色变更通知在绑定中的作用。除非数据源实现更改通知,否则数据源中的更改不能也不会传播到 UI。对于普通属性,有两种方法可以执行此操作:使数据源从
DependencyObject
派生并使绑定属性成为依赖属性,或者使数据源实现 INotifyPropertyChanged
并在属性值更改时引发 PropertyChanged
事件。将 ItemsControl
绑定到集合时,请使用实现 INotifyCollectionChanged
的集合类(如 ObservableCollection<T>
),以便对集合内容和顺序的更改将传播到绑定控件。 (请注意,如果您希望对集合中的项目进行更改以传播到绑定控件,这些项目也需要实现更改通知。)
public class CustomListBox : ListBox
{
#region Constants
public static new readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(CustomListBox), new PropertyMetadata(default(IList), OnSelectedItemsPropertyChanged));
#endregion
#region Properties
public new IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
#endregion
#region Event Handlers
private static void OnSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((CustomListBox)d).OnSelectedItemsChanged((IList)e.OldValue, (IList)e.NewValue);
}
protected virtual void OnSelectedItemsChanged(IList oldSelectedItems, IList newSelectedItems)
{
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
SetValue(SelectedItemsProperty, base.SelectedItems);
}
#endregion
}
公共分部类 ListBoxControl : UserControl { #区域常量
public static new readonly DependencyProperty ContentProperty =
DependencyProperty.Register(nameof(Content), typeof(object), typeof(ListBoxControl),
new PropertyMetadata(null));
public static new readonly DependencyProperty ContentTemplateProperty =
DependencyProperty.Register(nameof(ContentTemplate), typeof(DataTemplate), typeof(ListBoxControl),
new PropertyMetadata(null));
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(nameof(Items), typeof(IList), typeof(ListBoxControl),
new PropertyMetadata(null));
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(ListBoxControl),
new UIPropertyMetadata(null, OnSelectedItemsChanged));
#endregion
#region Properties
public new DataTemplate ContentTemplate
{
get => (DataTemplate)GetValue(ContentTemplateProperty);
set => SetValue(ContentTemplateProperty, value);
}
public IList Items
{
get => (IList)GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
#endregion
#region Constructors
public ListBoxControl()
{
InitializeComponent();
}
#endregion
#region Event Handlers
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not ListBoxControl || e.NewValue is not IList newValue)
{
return;
}
var mylist = (d as ListBoxControl).CustomList;
foreach (var selectedItem in newValue)
{
mylist.UpdateLayout();
if (mylist.ItemContainerGenerator.ContainerFromItem(selectedItem) is ListBoxItem selectedListBoxItem)
{
selectedListBoxItem.IsSelected = true;
}
}
}
#endregion
#region Private Methods
private void CheckAll_Click(object sender, RoutedEventArgs e)
{
CustomList.SelectAll();
}
private void UncheckAll_Click(object sender, RoutedEventArgs e)
{
CustomList.UnselectAll();
}
#endregion
}
#endregion
ListBoxControl.xaml
<UserControl x:Class="UserControls.ListBoxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:UserControls"
xmlns:str="Client.Properties"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="this">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<local:CustomListBox x:Name="CustomList"
Grid.Row="0"
Width="250"
HorizontalAlignment="Left"
SelectionMode="Multiple"
Visibility="Visible"
MinHeight="25"
MaxHeight="400"
ItemsSource="{Binding ElementName=this, Path =Items}"
SelectedItems="{Binding ElementName=this, Path =SelectedItems,Mode=TwoWay}"
Style="{StaticResource {x:Type ListBox}}"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<local:CustomListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Trigger>
<Trigger Property="IsMouseCaptureWithin" Value="true">
<Setter Property="IsSelected" Value="true" />
</Trigger>
<Trigger Property="IsMouseCaptureWithin" Value="False">
<Setter Property="IsSelected" Value="False" />
</Trigger>
</Style.Triggers>
</Style>
</local:CustomListBox.ItemContainerStyle>
<local:CustomListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<CheckBox Margin="4" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsSelected}" />
<ContentPresenter Content="{Binding .}" ContentTemplate="{Binding ElementName=this, Path = ContentTemplate, Mode=OneWay}"/>
</DockPanel>
</DataTemplate>
</local:CustomListBox.ItemTemplate>
</local:CustomListBox>
<Grid Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Left">
<Button Click="CheckAll_Click"
BorderBrush="Transparent"
ToolTip="Check all">
<Button.Content>
<Image Source="CheckAll.png" Height="16" Width="16"/>
</Button.Content>
</Button>
<Button
Click="UncheckAll_Click"
BorderBrush="Transparent"
Visibility="Visible"
ToolTip="Unchecked all">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=this, Path = SelectedItems.Count}" Value="0">
<Setter Property="Button.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.Content>
<Image Source="UncheckAll.png" Height="16" Width="16" />
</Button.Content>
</Button>
</StackPanel>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding ElementName=this, Path = SelectedItems.Count, StringFormat={x:Static str:Resources.STE_LABEL_X_ITEMS_CHECKED}, Mode=OneWay}"
HorizontalAlignment="Right" TextAlignment="Right" VerticalAlignment="Center"
Foreground="White" />
</Grid>
</Grid>
</UserControl>
现在您可以在任何控件或页面中使用该自定义控件并传递您想要的任何内容 例如:ConfigView.xaml
<UserControl ..
xmlns:userControls="Client.UserControls"
..>
<userControls:ListBoxControl
ShowCheckBox="True"
MinHeight="25"
MaxHeight="400"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Items="{Binding MyLists, Mode=OneWay}"
SelectedItems="{Binding SelectedMyLists,Mode=TwoWay}"
HorizontalAlignment="Left">
<userControls:ListBoxControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Name,StringFormat=' {0}'}" />
</StackPanel>
</DataTemplate>
</userControls:ListBoxControl.ContentTemplate>
</userControls:ListBoxControl>
private IList _myLists;
public IList MyLists
{
get => _myLists;
set
{
if (_myLists == value)
{
return;
}
_myLists = value;
OnPropertyChanged(nameof(SelectedItems));
}
}
public IEnumerable<MyModel> SelectedItems => MyLists.Cast<MyModel>();
<ListBox ItemsSource="{Binding Path=TopicList}"
CS
List<string> items = new List<string> { "Item 1", "Item 2", "Item 3" };
myListBox.ItemsSource = items;