[WPF] CheckListBox的实现方式分析

实际项目中常常要实现有CheckBox列表框。但是WPF没有自带这样的一个控件,下面就用Style来实现这样的功能。而对于CheckBox列表框,又常常会有一个Select AllCheckBox来表示当前列表框的选择状态。这个功能也会被包含在下面的示例之中。效果如下图所示。

 

对于单纯的,没有后台数据绑定的情况下,这个功能可以用ItemContainerStyle来实现。代码如下: 

 

CheckListBoxItemContainerStyle
<Style x:Key="CheckListBoxItemContainerStyle"
       TargetType
="{x:Type ListBoxItem}">
  
<!--Set it un-focusable, becaues the CheckBox in it should be focusable and only it.-->
  
<Setter Property="Focusable" Value="False"/>
  
<Setter Property="Template">
    
<Setter.Value>
      
<ControlTemplate TargetType="{x:Type ListBoxItem}">
        
<CheckBox Content="{TemplateBinding Content}"
              ContentTemplate
="{TemplateBinding ContentTemplate}"
              ContentTemplateSelector
="{TemplateBinding ContentTemplateSelector}"
              IsChecked
="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}}"/>
      
</ControlTemplate>
    
</Setter.Value>
  
</Setter>
</Style>

 

 

其中要对ContentContentTemplate等属性进行绑定,以方便对其进行扩展,保证其通用性。这个Style一般会放在Application级别的Resource中。

对于有后台数据绑定的情况,一般会有双个属性要绑定,一个是CheckBox里的Content,一个是CheckBoxIsChecked。绑定的路径,只有在用一个StyleListBox那里才知道,所以并不能写在这个Style里,否则会破坏这个Style的通用性。比较合理的方式是基于这个现有的Style进行修改。

 

对于下面的数据类。

DataItem Class
public class DataItem : INotifyPropertyChanged
{
    
private string name;
    
private bool isEnabled;

    
public string Name
    {
        
get { return name; }
        
set
        {
            name 
= value;
            OnPropertyChanged(
"Name");
        }
    }

    
public bool IsEnabled
    {
        
get { return isEnabled; }
        
set
        {
            isEnabled 
= value;
            OnPropertyChanged(
"IsEnabled");
        }
    }

    
#region INotifyPropertyChanged Members

    
public event PropertyChangedEventHandler PropertyChanged;

    
#endregion

    
protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler temp 
= PropertyChanged;
        
if (temp != null)
        {
            temp(
thisnew PropertyChangedEventArgs(propertyName));
        }
    }
}

 

 

我们需要下面这个有针对性的Style来应用数据绑定。

DataItemCheckListBoxStyle
<Style x:Key="DataItemCheckListBoxStyle"
       TargetType
="{x:Type ListBox}"
       BasedOn
="{StaticResource {x:Type ListBox}}">
  
<Setter Property="ItemTemplate">
    
<Setter.Value>
      
<DataTemplate>
        
<TextBlock Text="{Binding Name}"/>
      
</DataTemplate>
    
</Setter.Value>
  
</Setter>
  
<Setter Property="ItemContainerStyle">
    
<Setter.Value>
      
<Style TargetType="{x:Type ListBoxItem}"
             BasedOn
="{StaticResource CheckListBoxItemContainerStyle}">
        
<Setter Property="IsSelected"
                Value
="{Binding IsEnabled}"/>
        
<Setter Property="Margin" Value="2,2,0,0"/>
      
</Style>
    
</Setter.Value>
  
</Setter>
  
<Setter Property="SelectionMode" Value="Multiple"/>
</Style>

 

在上面的Style中,使用了ItemTemplate来指定CheckBox里的Content绑定到的属性,并把ListBoxItemIsSelected绑定数据的相应属性上。由于这个Style是针对特定数据写的,所以应当放置在使用这个StyleListBox所在的WindowResource中。

 

当然,也可以为ListBox添加两个绑定类型的Attached Property来实现一个通用的Style。不过这个Property一样要在使用的地方设置,其实没有太大区别。有兴趣的读者可以自己试一下。

 

对于Select All这个CheckBox而言,用Attached Property倒是很方便。给CheckBox添加一个SyncTarget属性指向要同步的ListBox,就可以在Window.xaml.cs之外的地方同步CheckBoxListBox了。代码如下:

 

ToggleButtonProperty
public class ToggleButtonProperty
{
    
// Using a DependencyProperty as the backing store for SyncTarget.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SyncTargetProperty =
        DependencyProperty.RegisterAttached(
"SyncTarget"typeof(ListBox), typeof(ToggleButtonProperty), new UIPropertyMetadata(new PropertyChangedCallback(OnSyncTargetChanged)));

    
public static ListBox GetSyncTarget(DependencyObject obj)
    {
        
return obj.GetValue(SyncTargetProperty) as ListBox;
    }

    
public static void SetSyncTarget(DependencyObject obj, ListBox value)
    {
        obj.SetValue(SyncTargetProperty, value);
    }

    
private static void OnSyncTargetChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        ToggleButton checker 
= sender as ToggleButton;
        
if (checker == null)
        {
            
throw new InvalidOperationException("SyncTarget property only works on ToggleButton.");
        }

        ListBox targetList 
= e.NewValue as ListBox;
        
if (targetList == null)
        {
            
throw new InvalidOperationException("Sync target must be a ListBox.");
        }

        
//TODO: Un-subscribe OldValue's Event.

        checker.Checked 
+= (s, a) =>
        {
            targetList.SelectAll();
        };

        checker.Unchecked 
+= (s, a) =>
        {
            targetList.UnselectAll();
        };

        targetList.SelectionChanged 
+= (s, a) =>
        {
            checker.IsChecked 
= targetList.SelectedItems.Count == 0 ? false :
                targetList.SelectedItems.Count 
== targetList.Items.Count ? (bool?)true : null;
        };
    }
}

 

使用方式也很简单。如下代码所示。

 

用法
<DockPanel Margin="12">
    
<CheckBox Content="Select All"
              Margin
="0,0,0,5"
              DockPanel.Dock
="Top"
              ext:ToggleButtonProperty.SyncTarget
="{Binding ElementName=checkListBox}"/>
    
<ListBox x:Name="checkListBox"
             Style
="{StaticResource DataItemCheckListBoxStyle}"
             ItemsSource
="{Binding Path=Items, ElementName=mainWindow}"/>
</DockPanel>

 

 

完整的项目文件可以从这里下载

posted on 2009-12-30 21:52  南柯之石  阅读(9174)  评论(3编辑  收藏  举报

导航