如何计算 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 等)。
通常,我建议避免在视图模型中反映视觉状态的属性。如果添加 IsVisibleIsPressedIsToggled 等属性,您的视图模型会变得臃肿。此属性属于 Control.

转换器(或 DataTrigger)方法使您的绑定数据模型保持不变(仅与数据相关的属性)。为了保持视图模型干净并且不受 UI 逻辑的影响,例如调整 IsVisibleIsChecked 等属性以反映例如将集合重新排序到视图(例如插入或排序操作),所有 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