开发者

WPF GridView with Multiple Checkbox Columns and Select All Column Header

开发者 https://www.devze.com 2023-03-05 00:48 出处:网络
I have a ListView containing several GridViewColumns. Several of the columns are checkboxes. Each column header consists of a checkbox as well, with the intention of checking/unchecking all the checkb

I have a ListView containing several GridViewColumns. Several of the columns are checkboxes. Each column header consists of a checkbox as well, with the intention of checking/unchecking all the checkboxes in that column. Each row's checkboxes are bound to properties in my view model. I've seen several postings where the scenario is a single column of checkboxes, but none of those solutions will work for me开发者_开发百科, as I have 4 columns of checkboxes. I also need to persist the state of the selections from one visit to the next (all, some or none of the checkboxes in a column could be checked).

Here's an abbreviated version of the XAML for my ListView:

<ListView ItemsSource="{Binding AveragingParameters}">
  <ListView.View>
    <GridView AllowsColumnReorder="False">

      <GridViewColumn Header="Parameter">
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <DockPanel>
              <TextBlock Text="{Binding Name}" />
            </DockPanel>
          </DataTemplate>
        </GridViewColumn.CellTemplate>
      </GridViewColumn>

      <GridViewColumn>
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <Grid HorizontalAlignment="Stretch">
              <CheckBox  x:Name="chkAvg" IsChecked="{Binding CalcAverage}" />
            </Grid>
          </DataTemplate>
        </GridViewColumn.CellTemplate>
        <Grid>
          <CheckBox x:Name="chkAvgSelectAll" Content="Avg" ToolTip="Select All" />
        </Grid>
      </GridViewColumn>

      <GridViewColumn>
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <Grid HorizontalAlignment="Stretch">
              <CheckBox  x:Name="chkMin" IsChecked="{Binding CalMin}" />
            </Grid>
          </DataTemplate>
        </GridViewColumn.CellTemplate>
        <Grid>
          <CheckBox x:Name="chkMinSelectAll" Content="Min" ToolTip="Select All" />
        </Grid>
      </GridViewColumn>

    </GridView>
  </ListView.View>
</ListView>

I've tried using Commands and Command Parameters, PropertyChanged events on the Checked property, and handling the Checked event in my code behind, but nothing gives me enough information to know what to change in the collection I'm bound to.

I suppose I could create a separate event handler for each, but I'd like to be able to handle this in a single event handler if possible, because eventually I will be creating a custom control that is a bit more malluable in terms of the columns displayed.

Thanks in advance


I've solved my issue using a Command along with a Tag containing the name of the model property I want set and a CommandParameter set to a multi-binding that is bound to the Tag and the Checkbox's IsSelected property. The code follows:

My View's Xaml Resources:

<UserControl.Resources>
   <converters:NameValueMultiBindingConverter x:Key="SelectAllConverter" />
</UserControl.Resources>

My View's Xaml:

<ListView ItemsSource="{Binding AveragingParameters}">
  <ListView.View>
    <GridView AllowsColumnReorder="False">

      <GridViewColumn Header="Parameter">
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <DockPanel>
              <TextBlock Text="{Binding Name}" />
            </DockPanel>
          </DataTemplate>
        </GridViewColumn.CellTemplate>
      </GridViewColumn>

      <GridViewColumn>
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <Grid HorizontalAlignment="Stretch">
              <CheckBox  x:Name="chkAvg" IsChecked="{Binding CalcAverage}" />
            </Grid>
          </DataTemplate>
        </GridViewColumn.CellTemplate>

        <CheckBox x:Name="chkAvgSelectAll" Content="Avg" 
                  Tag="CalcAvg" Command="SelectAllCheckedCommand" 
                  ToolTip="Select All">
            <MultiBinding Converter="{StaticResource SelectAllConverter}">
              <Binding Path="Tag" RelativeSource="{RelativeSource self}" />
              <Binding Path="IsChecked" RelativeSource="{RelativeSource self}" />
            </MultiBinding>
        </CheckBox>
      </GridViewColumn>

      <GridViewColumn>
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <Grid HorizontalAlignment="Stretch">
              <CheckBox  x:Name="chkMin" IsChecked="{Binding CalMin}" />
            </Grid>
          </DataTemplate>
        </GridViewColumn.CellTemplate>

        <CheckBox x:Name="chkMinSelectAll" Content="Avg" 
                  Tag="CalcMin" Command="SelectAllCheckedCommand" 
                  ToolTip="Select All">
            <MultiBinding Converter="{StaticResource SelectAllConverter}">
              <Binding Path="Tag" RelativeSource="{RelativeSource self}" />
              <Binding Path="IsChecked" RelativeSource="{RelativeSource self}" />
            </MultiBinding>
        </CheckBox>
      </GridViewColumn>

    </GridView>
  </ListView.View>
</ListView>

My MultiValueConverter:

public class NameValueMultiBindingConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter,
                          System.Globalization.CultureInfo culture)
    {

        var parameters = (object[])values;
        return new NameValueConverterResult 
                        { 
                           Name = (string)parameters[0], 
                           Value = parameters[1] 
                        };
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, 
                                System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The result object I'm using in the converter:

public class NameValueConverterResult
{
    //The name of the model property to set
    public string Name { get; set; }

    //The value we're setting it to
    public object Value { get; set; }
}

The DelegateCommand (I'm using PRISM) and handler in my View Model

public ICommand SelectAllCheckedCommand { get; private set; }
private void OnSelectAllCheckedCommand(object arg )
{
   if (arg == null || !(arg is NameValueConverterResult)) return;

   NameValueConverterResult prop = arg as NameValueConverterResult;

   //Reflect on the model to find the property we want to update.
   PropertyInfo propInfo = Averagers.FirstOrDefault().GetType().GetProperty(prop.Name);
   if (propInfo == null) return;

   //Set the property on the Model
   foreach (var item in Averagers)
      propInfo.SetValue(item, prop.Value, null);
} 

I hope I'm not abusing StackOverflow providing the solution I came up with. I wasn't sure of the etiquette here.


Just hand over as much info as needed and do the rest via reflection, e.g.

<ListView ItemsSource="{Binding DpData}">
    <ListView.View>
        <GridView>
            <GridViewColumn>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <CheckBox IsChecked="{Binding IsActive}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
                <!-- Pass collection to Tag property, relevant member info is in the Content,
                     could also create an array in the Tag if the Content should be a nicer
                     looking string. -->
                <CheckBox Content="IsActive" Click="CheckBox_Click" Tag="{Binding DpData}"/>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>
// This method could be used for all columns,
// as long as they contain the necessary info
// which should be provided in the XAML:
//     - Item Collection
//     - Property Name
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
    var cb = sender as CheckBox;
    var items = cb.Tag as IEnumerable;
    PropertyInfo prop = null;
    foreach (var item in items)
    {
        if (prop == null)
        {
            var type = item.GetType();
            var propname = cb.Content as string;
            prop = type.GetProperty(propname);
        }
        prop.SetValue(item, cb.IsChecked, null);
    }
}


If you want to stick with MVVM approach i would suggest to go for binding approach rather than event handling approach as MVVM avoids writing code in code behind file.

One way could be to simply bind two bool properties in view model with IsChecked Property of both checkboxes. Based on the change notification using INotifyPropetyChanged, you can iterate through your itemssource i.e. AveragingParameters and change either CalMin or CalcAverage

0

精彩评论

暂无评论...
验证码 换一张
取 消