如何计算 ItemsControl 中选中的复选框
How To Count Checked CheckBox Inside ItemsControl
我在 WPF 中使用 ItemsControl
创建了多个复选框。但是我需要为用户可以 checked/ticked 的复选框限制 20 个。如何检查已选中的复选框?
我尝试尽可能多地研究它,甚至将复选框绑定到多个命令,但是 none 它正在工作。下面是我的代码,用于通过 Itemscontrol
内的复选框。之后,IsChecked
.
for (int i = 0; i < ItemsControlUnitPerStrip.Items.Count; i++)
{
ContentPresenter container = (ContentPresenter)ItemsControlUnitPerStrip.ItemContainerGenerator.ContainerFromItem(ItemsControlUnitPerStrip.Items[i]);
CheckBox checkBoxChecked = container.ContentTemplate.FindName("CheckBoxUnitPerStrip", container) as CheckBox;
if (checkBoxChecked.IsChecked == true)
{
//iOPC.WriteTag(checkBoxChecked.Uid, checkBoxChecked.IsChecked);
}
}
我的XAML代码
<GroupBox x:Name="GroupBoxSamplingModeStrip" Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ItemsControlUnitPerStrip"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
这里是我如何生成复选框的函数代码
private void initializeUnitPerStrip()
{
unitPerStrip = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
unitPerStrip.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
});
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
此答案使用 MVVM,因此 XAML 中的控件名称已被删除,因为 MVVM 不需要它们。您的 XAML 将如下所示:
<Button Content="Count CheckBoxes" Command="{Binding CommandCount}"
HorizontalAlignment="Left"/>
<GroupBox Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ItemsSource="{Binding Path=UnitPerStrip}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip >
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
XAML 中唯一真正的区别是在 CheckBox 中添加绑定到 'IsChecked' 属性,并设置绑定到名为 属性 的 属性 =28=] 用于 ItemsControl 的 ItemsSource。
然后在ViewModel中,需要设置UnitPerStrip 属性:
private List<UtilitiesModel> unitPerStrip;
public List<UtilitiesModel> UnitPerStrip
{
get
{
return unitPerStrip;
}
set
{
if (value != unitPerStrip)
{
unitPerStrip = value;
NotifyPropertyChanged("UnitPerStrip");
}
}
}
UtilitiesModel class 需要一个名为 IsChecked 的新 属性 来跟踪 CheckBox 何时被选中。这样您就不必处理混乱的 UI 代码。都可以在后台数据里干干净净
public class UtilitiesModel
{
public string Key { get; set; }
public string Tag { get; set; }
public bool IsChecked { get; set; }
}
生成复选框的代码没有太大变化。您只需要确保添加 IsChecked 属性,然后在完成后将结果分配给 UnitPerStrip 属性。
private void initializeUnitPerStrip()
{
List<UtilitiesModel> ups = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
ups.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}",
IsChecked = false;
});
}
}
UnitPerStrip = ups;
}
然后检查有多少复选框被选中的代码就非常简单了。它只检查 ViewModel 中的数据,而不必担心弄乱 UI:
的任何混乱
private void Count()
{
int count = 0;
foreach (UtilitiesModel item in UnitPerStrip)
{
if (item.IsChecked) count++;
}
MessageBox.Show(count.ToString());
}
如果您不想在模型中添加特殊的 IsChecked
属性,您可以使用 IValueConverter
.
像 IsChecked
这样的 属性 与视图相关,不应成为视图模型的一部分(如果可以避免的话)。当您更改绑定到视图模型的控件时,您可能还需要更改此 属性 或重命名它(例如 IsExpanded
等)。
通常,我建议避免在视图模型中反映视觉状态的属性。如果添加 IsVisible
、IsPressed
、IsToggled
等属性,您的视图模型会变得臃肿。此属性属于 Control
.
转换器(或 DataTrigger
)方法使您的绑定数据模型保持不变(仅与数据相关的属性)。为了保持视图模型干净并且不受 UI 逻辑的影响,例如调整 IsVisible
或 IsChecked
等属性以反映例如将集合重新排序到视图(例如插入或排序操作),所有 UI 逻辑和视觉细节,如启用或禁用控件
应仅由转换器和触发器处理:
<!-- Decalare the converter and set the MaxCount property -->
<Window.Resources>
<local:ItemCountToBoolenaConverter x:Key="ItemCountToBoolenaConverter"
MaxCount="20" />
</Window.Resources>
<! -- The actual DataTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Converter={StaticResource ItemCountToBoolenaConverter}}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
ItemCountToBoolenaConverter
:
[ValueConversion(typeof(ListBoxItem), typeof(bool))]
class ItemCountToBoolenaConverter : IValueConverter
{
public int MaxCount { get; set; }
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ListBoxItem itemContainer && TryFindParentElement(itemContainer, out ItemsControl parentItemsControl))
{
return parentItemsControl.Items.IndexOf(itemContainer.Content) < this.MaxCount;
}
return Binding.DoNothing;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
// Consider to make this an Extension Method for DependencyObject
private bool TryFindVisualParent<TParent>(DependencyObject child, out TParent resultElement) where TParent : DependencyObject
{
resultElement = null;
if (child == null)
return false;
DependencyObject parentElement = VisualTreeHelper.GetParent(child);
if (parentElement is TParent)
{
resultElement = parentElement as TParent;
return true;
}
return TryFindVisualParent(parentElement, out resultElement);
}
}
1) 绑定复选框 属性 与通知 属性 更改事件:
public class UtilitiesModel : NotifyBase
{
private bool _IsChecked = false;
...
// Key
// Tag
...
public bool IsChecked
{
get {return _IsChecked;}
set
{
_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
为了方便起见,负责事件的部分单独放在一个小的class:
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
XAML 变化:
..
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
..
2) 接下来我们将跟踪复选框状态变化的事件,并为选中的复选框添加一个计数器;
功能略有变化:
private void initializeUnitPerStrip()
{
..
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
UtilitiesModel item = new UtilitiesModel
{
Key = "[{c}, {r}]",
Tag = "{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
};
item.PropertyChanged += PropertyChangedFunc;
unitPerStrip.Add(item);
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
添加功能以检查 属性 更改的事件:
private void PropertyChangedFunc(object sender, PropertyChangedEventArgs e)
{
UtilitiesModel obj = sender as UtilitiesModel;
if(obj==null)return;
if (e.PropertyName == "IsChecked")
{
iCount1 = obj.IsChecked ? iCount1 + 1 : iCount1 - 1;
if (iCount1 > 19) //Block checking
{
obj.IsChecked = false;
}
}
}
其中 iCount1 - 是一个计数器选中的复选框,只需在任何地方声明它,例如在 samplingModeModel
我在 WPF 中使用 ItemsControl
创建了多个复选框。但是我需要为用户可以 checked/ticked 的复选框限制 20 个。如何检查已选中的复选框?
我尝试尽可能多地研究它,甚至将复选框绑定到多个命令,但是 none 它正在工作。下面是我的代码,用于通过 Itemscontrol
内的复选框。之后,IsChecked
.
for (int i = 0; i < ItemsControlUnitPerStrip.Items.Count; i++)
{
ContentPresenter container = (ContentPresenter)ItemsControlUnitPerStrip.ItemContainerGenerator.ContainerFromItem(ItemsControlUnitPerStrip.Items[i]);
CheckBox checkBoxChecked = container.ContentTemplate.FindName("CheckBoxUnitPerStrip", container) as CheckBox;
if (checkBoxChecked.IsChecked == true)
{
//iOPC.WriteTag(checkBoxChecked.Uid, checkBoxChecked.IsChecked);
}
}
我的XAML代码
<GroupBox x:Name="GroupBoxSamplingModeStrip" Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="ItemsControlUnitPerStrip"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
这里是我如何生成复选框的函数代码
private void initializeUnitPerStrip()
{
unitPerStrip = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
unitPerStrip.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
});
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
此答案使用 MVVM,因此 XAML 中的控件名称已被删除,因为 MVVM 不需要它们。您的 XAML 将如下所示:
<Button Content="Count CheckBoxes" Command="{Binding CommandCount}"
HorizontalAlignment="Left"/>
<GroupBox Header="Unit Per Strip" Grid.Row="0" Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ItemsSource="{Binding Path=UnitPerStrip}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="{Binding StripRowsCount}"
Columns="{Binding StripColumnsCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip >
<TextBlock Text="{Binding Key}"/>
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</GroupBox>
XAML 中唯一真正的区别是在 CheckBox 中添加绑定到 'IsChecked' 属性,并设置绑定到名为 属性 的 属性 =28=] 用于 ItemsControl 的 ItemsSource。
然后在ViewModel中,需要设置UnitPerStrip 属性:
private List<UtilitiesModel> unitPerStrip;
public List<UtilitiesModel> UnitPerStrip
{
get
{
return unitPerStrip;
}
set
{
if (value != unitPerStrip)
{
unitPerStrip = value;
NotifyPropertyChanged("UnitPerStrip");
}
}
}
UtilitiesModel class 需要一个名为 IsChecked 的新 属性 来跟踪 CheckBox 何时被选中。这样您就不必处理混乱的 UI 代码。都可以在后台数据里干干净净
public class UtilitiesModel
{
public string Key { get; set; }
public string Tag { get; set; }
public bool IsChecked { get; set; }
}
生成复选框的代码没有太大变化。您只需要确保添加 IsChecked 属性,然后在完成后将结果分配给 UnitPerStrip 属性。
private void initializeUnitPerStrip()
{
List<UtilitiesModel> ups = new List<UtilitiesModel>();
int totalRow = samplingModeModel.StripRows = 7;
int totalCol = samplingModeModel.StripColumn = 15;
int frontOffset = 8;
int behindOffset = 0;
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
ups.Add(new UtilitiesModel
{
Key = $"[{c}, {r}]",
Tag = $"{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}",
IsChecked = false;
});
}
}
UnitPerStrip = ups;
}
然后检查有多少复选框被选中的代码就非常简单了。它只检查 ViewModel 中的数据,而不必担心弄乱 UI:
的任何混乱 private void Count()
{
int count = 0;
foreach (UtilitiesModel item in UnitPerStrip)
{
if (item.IsChecked) count++;
}
MessageBox.Show(count.ToString());
}
如果您不想在模型中添加特殊的 IsChecked
属性,您可以使用 IValueConverter
.
像 IsChecked
这样的 属性 与视图相关,不应成为视图模型的一部分(如果可以避免的话)。当您更改绑定到视图模型的控件时,您可能还需要更改此 属性 或重命名它(例如 IsExpanded
等)。
通常,我建议避免在视图模型中反映视觉状态的属性。如果添加 IsVisible
、IsPressed
、IsToggled
等属性,您的视图模型会变得臃肿。此属性属于 Control
.
转换器(或 DataTrigger
)方法使您的绑定数据模型保持不变(仅与数据相关的属性)。为了保持视图模型干净并且不受 UI 逻辑的影响,例如调整 IsVisible
或 IsChecked
等属性以反映例如将集合重新排序到视图(例如插入或排序操作),所有 UI 逻辑和视觉细节,如启用或禁用控件
应仅由转换器和触发器处理:
<!-- Decalare the converter and set the MaxCount property -->
<Window.Resources>
<local:ItemCountToBoolenaConverter x:Key="ItemCountToBoolenaConverter"
MaxCount="20" />
</Window.Resources>
<! -- The actual DataTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="CheckBoxUnitPerStrip"
IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Converter={StaticResource ItemCountToBoolenaConverter}}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
ItemCountToBoolenaConverter
:
[ValueConversion(typeof(ListBoxItem), typeof(bool))]
class ItemCountToBoolenaConverter : IValueConverter
{
public int MaxCount { get; set; }
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ListBoxItem itemContainer && TryFindParentElement(itemContainer, out ItemsControl parentItemsControl))
{
return parentItemsControl.Items.IndexOf(itemContainer.Content) < this.MaxCount;
}
return Binding.DoNothing;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
// Consider to make this an Extension Method for DependencyObject
private bool TryFindVisualParent<TParent>(DependencyObject child, out TParent resultElement) where TParent : DependencyObject
{
resultElement = null;
if (child == null)
return false;
DependencyObject parentElement = VisualTreeHelper.GetParent(child);
if (parentElement is TParent)
{
resultElement = parentElement as TParent;
return true;
}
return TryFindVisualParent(parentElement, out resultElement);
}
}
1) 绑定复选框 属性 与通知 属性 更改事件:
public class UtilitiesModel : NotifyBase
{
private bool _IsChecked = false;
...
// Key
// Tag
...
public bool IsChecked
{
get {return _IsChecked;}
set
{
_IsChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
为了方便起见,负责事件的部分单独放在一个小的class:
public class NotifyBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
XAML 变化:
..
<CheckBox x:Name="CheckBoxUnitPerStrip"
Uid="{Binding Tag}"
IsChecked="{Binding IsChecked}">
<CheckBox.ToolTip>
<ToolTip x:Name="TootlTipUnitPerStrip">
<TextBlock Text="{Binding Key}" />
</ToolTip>
</CheckBox.ToolTip>
</CheckBox>
..
2) 接下来我们将跟踪复选框状态变化的事件,并为选中的复选框添加一个计数器;
功能略有变化:
private void initializeUnitPerStrip()
{
..
for (int c = 1; c < totalCol; c++)
{
for (int r = 1; r < totalRow; r++)
{
UtilitiesModel item = new UtilitiesModel
{
Key = "[{c}, {r}]",
Tag = "{UTAC_Tags.S7Connection}DB{406},X{frontOffset}.{behindOffset}"
};
item.PropertyChanged += PropertyChangedFunc;
unitPerStrip.Add(item);
}
}
ItemsControlUnitPerStrip.ItemsSource = unitPerStrip;
}
添加功能以检查 属性 更改的事件:
private void PropertyChangedFunc(object sender, PropertyChangedEventArgs e)
{
UtilitiesModel obj = sender as UtilitiesModel;
if(obj==null)return;
if (e.PropertyName == "IsChecked")
{
iCount1 = obj.IsChecked ? iCount1 + 1 : iCount1 - 1;
if (iCount1 > 19) //Block checking
{
obj.IsChecked = false;
}
}
}
其中 iCount1 - 是一个计数器选中的复选框,只需在任何地方声明它,例如在 samplingModeModel