how to binding something to groupstyle's header - wpf

Using CollectionViewSource can easily grouping the ListBoxItem.
But each group Header, only have a "Name" data.
How to put more data to the Header?
like the picture.
i want to use a custom control in the header, and binding a viewmodel to it.
but i dont know how to get the viewmodel from the grouping parent.
and, the <ItemsPresenter/>, groupItem's part.if i want binding something to it,
what can i do?
and , my code:
<ListBox ItemsSource="{Binding Source={StaticResource XamlBookMarks}}"
ItemTemplate="{StaticResource TemplateBookMark}">
<ListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource StyleBookMarkGroup}" />
</ListBox.GroupStyle>
</ListBox>
<Style x:Key="StyleWorkSiteGroup" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource TemplateHeader}" />
<!--here, i want binding something to ItemsPresenter-->
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="TemplateHeader" DataType="{x:Type GroupItem}">
<DockPanel>
<DockPanel.Resources>
<converters:HeaderGetter x:Key="HeaderConverter" />
</DockPanel.Resources>
<ContentControl DockPanel.Dock="Top" Content="{Binding Name, Converter={StaticResource HeaderConverter}, ConverterParameter={StaticResource XamlHeaders}}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:GroupOneHeaderViewModel}">
<views:GroupOneHeaderView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:GroupTwoHeaderViewModel}">
<views:GroupTwoHeaderView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</DataTemplate>

I made a wrapper class , but feel very ugly . Is there any other way to do this ?
1,get the Transition class from
http://www.11011.net/wpf-transitions
2.and
public class GroupItemsSelector : FrameworkElement
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source",
typeof (CollectionViewSource),
typeof (GroupItemsSelector),
new PropertyMetadata(OnSourceChangedCallback));
public static readonly DependencyProperty IdProperty =
DependencyProperty.Register("Id",
typeof (string),
typeof (GroupItemsSelector),
new PropertyMetadata(OnSourceChangedCallback));
public static readonly DependencyProperty CurrentProperty =
DependencyProperty.Register("Current",
typeof (object),
typeof (GroupItemsSelector),
new PropertyMetadata(OnCurrentItemChanged));
public static readonly DependencyProperty WatchingProperty =
DependencyProperty.Register("Watching",
typeof (string),
typeof (GroupItemsSelector),
new PropertyMetadata(OnCurrentItemChanged));
public static readonly DependencyProperty WatchingValueProperty =
DependencyProperty.Register("WatchingValue",
typeof (object),
typeof (GroupItemsSelector));
private readonly Transition _transition = new Transition();
public GroupItemsSelector()
{
var dpd = DependencyPropertyDescriptor.FromProperty(Transition.StateProperty, typeof (Transition));
if (dpd == null) return;
dpd.AddValueChanged(_transition, delegate { WatchingValue = _transition.Source; });
}
public object WatchingValue
{
get { return GetValue(WatchingValueProperty); }
private set { SetValue(WatchingValueProperty, value); }
}
public string Watching
{
get { return (string) GetValue(WatchingProperty); }
set { SetValue(WatchingProperty, value); }
}
public CollectionViewSource Source
{
get { return (CollectionViewSource) GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public string Id
{
get { return (string) GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public object Current
{
get { return GetValue(CurrentProperty); }
private set { SetValue(CurrentProperty, value); }
}
private static void OnCurrentItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as GroupItemsSelector;
if (selector == null) return;
selector.SetWatchingProperty();
}
private static void OnSourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as GroupItemsSelector;
if (selector == null) return;
selector.SetCurrent();
}
private void SetCurrent()
{
IDictionary dictionary = null;
bool hasValue = false;
try
{
if (Source == null || string.IsNullOrEmpty(Id)) return;
dictionary = Source.Source as IDictionary;
if (dictionary == null) return;
hasValue = dictionary.Contains(Id);
}
finally
{
Current = (hasValue) ? dictionary[Id] : null;
}
}
private void SetWatchingProperty()
{
_transition.DataContext = Current as INotifyPropertyChanged;
if (string.IsNullOrEmpty(Watching))
{
BindingOperations.ClearBinding(_transition, Transition.SourceProperty);
}
else
{
_transition.SetBinding(Transition.SourceProperty, new Binding(Watching));
}
}
}
3. and sample
<Style x:Key="StyleGroupItem" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<converters:GroupItemsSelector x:Name="selector" Id="{Binding Name}" Source="{StaticResource XamlHeaders}" Watching="IsExpanded"/>
<ContentControl Content="{Binding ElementName=selector, Path=Current}" >
</ContentControl>
<ItemsPresenter Visibility="{Binding ElementName=selector, Path=WatchingValue, Converter={x:Static converters:BoolVisiblilityConverter.Instance}}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Related

WPF Drawing a list of rectangles in a collection

My WPF app has a ViewModel that has an ObservableCollection that holds objects of type Item. Each Item has a color and a Rect that is drawn on the canvas:
Item Class:
public class Item
{
public Color ItemColor {get; set;}
public Rect ScaledRectangle {get; set;}
}
XAML:
<Grid>
<ItemsControl Name="Items" ItemsSource="{Binding Items, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ItemView Visibility="Visible">
<local:ItemView.Background>
<SolidColorBrush Color="{Binding ItemColor}"/>
</local:ItemView.Background>
</local:ItemView>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding ScaledRectangle.Left}"/>
<Setter Property="Canvas.Top" Value="{Binding ScaledRectangle.Top}"/>
<Setter Property="FrameworkElement.Width" Value="{Binding ScaledRectangle.Width}"/>
<Setter Property="FrameworkElement.Height" Value="{Binding ScaledRectangle.Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
In my ViewModel, all I have to do is add a new Item to the ObservableCollection to draw it on the screen.
This works really well but now I find I need to change the ScaledRectangle property to some kind of collection. I want to modify this XAML to draw each rectangle in the ScaledRectangles collection. Can I modify this XAML so I can keep the ViewModel functionality to something like viewModel.AddNewItem(newItem)?
You must modify your ItemsView to support handling of a collection of Rect instead of a single Rect:
ItemsView.cs
public class ItemsView : Control
{
public Item DataSource
{
get => (Item)GetValue(DataSourceProperty);
set => SetValue(DataSourceProperty, value);
}
public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register(
"DataSource",
typeof(Item),
typeof(ItemsView),
new PropertyMetadata(default(Item), OnDataSourceChanged));
private Panel ItemsHost { get; set; }
private Dictionary<Rect, int> ContainerIndexTable { get; }
static ItemsView()
=> DefaultStyleKeyProperty.OverrideMetadata(typeof(ItemsView), new FrameworkPropertyMetadata(typeof(ItemsView)));
public ItemsView()
=> this.ContainerIndexTable = new Dictionary<Rect, int>();
private static void OnDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as ItemsView;
this_.UnloadRectangles(e.OldValue as Item);
this_.LoadRectangles(e.NewValue as Item);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.ItemsHost = GetTemplateChild("PART_ItemsHost") as Panel;
LoadRectangles(this.DataSource);
}
private void UnloadRectangles(Item item)
{
if (item is null
|| this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in item.ScaledRectangles)
{
if (this.ContainerIndexTable.TryGetValue(rectangleDefinition, out int containerIndex))
{
this.ItemsHost.Children.RemoveAt(containerIndex);
}
}
}
private void LoadRectangles(Item item)
{
if (item is null
|| this.ItemsHost is null)
{
return;
}
foreach (Rect rectangleDefinition in item.ScaledRectangles)
{
var container = new Rectangle()
{
Height = rectangleDefinition.Height,
Width = rectangleDefinition.Width,
Fill = new SolidColorBrush(item.ItemColor)
};
Canvas.SetLeft(container, rectangleDefinition.Left);
Canvas.SetTop(container, rectangleDefinition.Top);
int containerIndex = this.ItemsHost.Children.Add(container);
_ = this.ContainerIndexTable.TryAdd(rectangleDefinition, containerIndex);
}
}
}
Gernic.xaml
<Style TargetType="local:ItemsView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ItemsView">
<Canvas x:Name="PART_ItemsHost" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<local:ItemsView DataSource="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

How to make a reusable WPF Custom control with data binding properties?

If I build a custom control with some controls inside it (witch also have some bindings), how can I remove the binding parts from the custom control XAML (like Text="{Binding Path=Name}" and ItemsSource="{Binding}") to make the control reusable? My guess is to create some dependency properties but I don't know how to do this and what makes it harder for me is that some bindings are inside the DataTemplate of the custom control and I can't get the instances by GetTemplateChild().
Here is a my code:
Custom Control:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
}
Generics.xaml:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="BindingTextBox" Text="{Binding Path=Name}"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml:
<StackPanel>
<local:CustomListBox x:Name="BindingCustomListBox"></local:CustomListBox>
</StackPanel>
MainWindow.xaml.cs And Person(Sample Data) Class:
public partial class MainWindow : Window
{
public ObservableCollection<Person> PersonList { get; set; }
public MainWindow()
{
InitializeComponent();
PersonList = new ObservableCollection<Person>
{
new Person{ Name = "Person1" },
new Person{ Name = "Person2" }
};
BindingCustomListBox.DataContext = PersonList;
}
}
public class Person
{
public string Name { get; set; }
}
by removing binding parts I mean moving from custom control to Window.xaml or where ever the user wants to use the control.
I hope it's clear enough.
And an ItemsSource property to your control:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustomListBox));
}
Bind to it in your template:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{TemplateBinding ItemsSource}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="BindingTextBox" Text="{Binding Name}"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and in your view:
<local:CustomListBox x:Name="BindingCustomListBox" ItemsSource="{Binding PersonList}" />
I found a solution though not sure if a better one exists(I didn't find any after 3 days). first I added a dependency property (NameBindingStr) to enable users to define the PropertyPath of the binding:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
public static readonly DependencyProperty NameBindingStrProperty =
DependencyProperty.Register(
"NameBindingStr", typeof(string), typeof(CustomListBox),
new FrameworkPropertyMetadata(""));
public string NameBindingStr
{
get { return (string)GetValue(NameBindingStrProperty); }
set { SetValue(NameBindingStrProperty, value); }
}
}
And the XAML for the custom control:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:BindTextBox TextBindingPath="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomListBox}}, Path=NameBindingStr, Mode=TwoWay}"></local:BindTextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
for the custom control's items to bind I inherited BindTextBox from TextBox:
public class BindTextBox : TextBox
{
static BindTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BindTextBox), new FrameworkPropertyMetadata(typeof(BindTextBox)));
}
public static readonly DependencyProperty TextBindingPathProperty =
DependencyProperty.Register(
"TextBindingPath", typeof(string), typeof(BindTextBox),
new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnTextBindingPathChanged)));
public string TextBindingPath
{
get { return (string)GetValue(TextBindingPathProperty); }
set { SetValue(TextBindingPathProperty, value); }
}
private static void OnTextBindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
BindTextBox elem = obj as BindTextBox;
var newTextBinding = new Binding((string)args.NewValue);
newTextBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(elem, TextProperty, newTextBinding);
}
}
XAML:
<Style TargetType="{x:Type local:BindTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BindTextBox}">
<TextBox x:Name="TemplateTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The MainWindow.xaml.cs is not changed and I won't type it again(can be found in the question). I have to recall that my goal was to let the user to easily set the binding path. Now the the custom control can be used by one single code:
<local:CustomListBox x:Name="BindingCustomListBox" NameBindingStr="Name"></local:CustomListBox>
Works perfect.

What the correct way to Binding ObservableCollection<Block> to ItemsControl that stored in FlowDocument's Paragraph?

I trying to binding ObservableCollection to ItemsControl that stored in FlowDocument's Paragraph but the blocks doesn't design the content as expected..
I have a custom control of wrapped control, as follow:
public class HelpCtrl : Control
{
static HelpCtrl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HelpCtrl), new FrameworkPropertyMetadata(typeof(HelpCtrl)));
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(HelpCtrl), new UIPropertyMetadata(null, OnTextChanged));
static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MemoryStream stream = new MemoryStream(File.ReadAllBytes(e.NewValue.ToString()));
RichTextBox r = new RichTextBox();
r.Selection.Load(stream, DataFormats.Rtf);
HelpCtrl helpCtrl = d as HelpCtrl;
ObservableCollection<Block> l = new ObservableCollection<Block>();
while (r.Document.Blocks.Count > 0)
{
var block = r.Document.Blocks.FirstBlock;
r.Document.Blocks.Remove(block);
l.Add(block);
}
helpCtrl.Blocks = l;
}
public ObservableCollection<Block> Blocks
{
get { return (ObservableCollection<Block>)GetValue(BlocksProperty); }
set { SetValue(BlocksProperty, value); }
}
public static readonly DependencyProperty BlocksProperty =
DependencyProperty.Register("Blocks", typeof(ObservableCollection<Block>), typeof(HelpCtrl), new UIPropertyMetadata(null));
}
<Style TargetType="{x:Type local:HelpCtrl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:HelpCtrl}">
<Grid>
<Grid x:Name="gridRichTextBox" Background="Black" Width="350" Height="600">
<RichTextBox
IsReadOnly="True"
x:Name="rtbHelpText"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
>
<FlowDocument>
<Paragraph>
<ItemsControl ItemsSource="{Binding Blocks, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:HelpCtrl}}}"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I load from a rtf file the content and design it, but i see that the binding isn't good, the results:
"System.Windows.Documents.Paragraph".
How to fix it?

Using DataGridComboBoxColumn as autocompletecombobox in a DataGrid

I want to use the DataGridComboBoxColumn as a autocomplete combobox.
I've got it partially working. When the Row is in EditMode I can type text in the ComboBox, also in ViewMode the control returns the text. Only how to get the Label (in template) to EditMode by mouse doubleclick?
Up front, I don't want to use the DataGridTemplateColumn control because it just doesn't handle keyboard and mouse entry like the DataGridComboBoxColumn does (tabs, arrows, edit/view mode/ double click etc..).
It looks like:
I fixed it adding a behavior to the TextBox to get a link to the parent DataGrid then setting the Row into Edit Mode by calling BeginEdit().
The solution I used:
View
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Model.Things}" Name="MyGrid" ClipboardCopyMode="IncludeHeader">
<DataGrid.Resources>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Object" MinWidth="140" TextBinding="{Binding ObjectText}" ItemsSource="{Binding Source={StaticResource proxy}, Path=Data.Model.ObjectList}" >
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsEditable" Value="True"/>
<Setter Property="Text" Value="{Binding ObjectText}"/>
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBox IsReadOnly="True" Text="{Binding Path=DataContext.ObjectText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}">
<TextBox.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="local:CellSelectedBehavior.IsCellRowSelected" Value="true"></Setter>
</Style>
</TextBox.Resources>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Model
public class Model : BaseModel
{
//List of objects for combobox
private List<string> _objectList;
public List<string> ObjectList { get { return _objectList; } set { _objectList = value; } }
//Rows in datagrid
private List<Thing> _things;
public List<Thing> Things
{
get { return _things; }
set { _things = value; OnPropertyChanged("Things"); }
}
}
public class Thing : BaseModel
{
//Text in combobox
private string _objectText;
public string ObjectText
{
get { return _objectText; }
set { _objectText = value; OnPropertyChanged("ObjectText"); }
}
}
ViewModel
public class ViewModel
{
public Model Model { get; set; }
public ViewModel()
{
Model = new WpfApplication1.Model();
Model.ObjectList = new List<string>();
Model.ObjectList.Add("Aaaaa");
Model.ObjectList.Add("Bbbbb");
Model.ObjectList.Add("Ccccc");
Model.Things = new List<Thing>();
Model.Things.Add(new Thing() { ObjectText = "Aaaaa" });
}
}
Behavior
public class CellSelectedBehavior
{
public static bool GetIsCellRowSelected(DependencyObject obj) { return (bool)obj.GetValue(IsCellRowSelectedProperty); }
public static void SetIsCellRowSelected(DependencyObject obj, bool value) { obj.SetValue(IsCellRowSelectedProperty, value); }
public static readonly DependencyProperty IsCellRowSelectedProperty = DependencyProperty.RegisterAttached("IsCellRowSelected",
typeof(bool), typeof(CellSelectedBehavior), new UIPropertyMetadata(false, OnIsCellRowSelected));
static void OnIsCellRowSelected(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TextBox item = depObj as TextBox;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.MouseDoubleClick += SelectRow;
else
item.MouseDoubleClick -= SelectRow;
}
static void SelectRow(object sender, EventArgs e)
{
TextBox box = sender as TextBox;
var grid = box.FindAncestor<DataGrid>();
grid.BeginEdit();
}
}
Helper (to find DataGrid)
public static class Helper
{
public static T FindAncestor<T>(this DependencyObject current) where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
}

Binding/Triggering "Select all"-CheckBox ComboBoxItem in WPF

I'm trying to make a WPF CustomControl CheckComboBox with a "Select All" item in addition to a user defined list of items. When "Select All" is selected, all items in the list should be checked accordingly. How can I act to the "Select All" item being clicked? I have tried a lot of things, but the property "SelectAll" in the CheckComboBox.cs is never entered.
This is my current code.
Generic.xaml
<Style TargetType="{x:Type local:CheckComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CheckComboBox}">
<ComboBox SelectedItem="{TemplateBinding SelectedItem}"
SelectedValue="{TemplateBinding SelectedValue}"
SelectedValuePath="{TemplateBinding SelectedValuePath}"
DisplayMemberPath="{TemplateBinding DisplayMemberPath}"
IsTextSearchEnabled="{TemplateBinding IsTextSearchEnabled}"
ItemTemplate="{TemplateBinding ItemTemplate}"
x:Name="InnerComboBox" >
<ComboBox.Resources>
<ResourceDictionary>
<CheckBox x:Key="allItem" Content="All" IsChecked="{Binding SelectAll, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
<CollectionViewSource x:Key="items" Source="{Binding ComboBoxItems, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
</ResourceDictionary>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem Content="{Binding Source={StaticResource allItem}}"/>
<CollectionContainer Collection="{Binding Source={StaticResource items}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Text}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
CheckComboBox.cs
public class CheckComboBox : ComboBox
{
public class CheckComboBoxItem
{
public CheckComboBoxItem(bool isSelected, string text)
{
IsSelected = isSelected;
Text = text;
}
public bool IsSelected { get; set; }
public string Text { get; set; }
}
static CheckComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckComboBox), new FrameworkPropertyMetadata(typeof(CheckComboBox)));
}
public static readonly DependencyProperty ComboBoxItemsProperty =
DependencyProperty.Register("ComboBoxItems", typeof (ObservableCollection<CheckComboBoxItem>), typeof (CheckComboBox), new PropertyMetadata(default(ObservableCollection<CheckComboBoxItem>)));
public ObservableCollection<CheckComboBoxItem> ComboBoxItems
{
get { return (ObservableCollection<CheckComboBoxItem>) GetValue(ComboBoxItemsProperty); }
set { SetValue(ComboBoxItemsProperty, value); }
}
public static readonly DependencyProperty SelectAllProperty =
DependencyProperty.Register("SelectAll", typeof (bool), typeof (CheckComboBox), new PropertyMetadata(default(bool)));
public bool SelectAll
{
get { return (bool) GetValue(SelectAllProperty); }
set
{
foreach (var item in ComboBoxItems)
{
item.IsSelected = value;
}
SetValue(SelectAllProperty, value);
}
}
}
}
Setting test data:
ObservableCollection<CheckComboBox.CheckComboBoxItem> checkComboBoxItems = new ObservableCollection<CheckComboBox.CheckComboBoxItem>();
checkComboBoxItems.Add(new CheckComboBox.CheckComboBoxItem(false, "Generation 0"));
checkComboBoxItems.Add(new CheckComboBox.CheckComboBoxItem(true, "Generation 1"));
checkComboBoxItems.Add(new CheckComboBox.CheckComboBoxItem(false, "Generation 2"));
checkComboBox1.ComboBoxItems = checkComboBoxItems;
Edit:
Replaced the SelectAll DependencyProperty in CheckComboBox.cs with the following code, but OnSelectAll is not entered. The SelectAll combobox does not trigger the binding for some reason.
public static readonly DependencyProperty SelectAllProperty =
DependencyProperty.Register("SelectAll",
typeof (bool),
typeof (CheckComboBox),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnSelectAll)));
private static void OnSelectAll(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CheckComboBox checkComboBox = (CheckComboBox)d;
foreach (var item in checkComboBox.ComboBoxItems)
{
item.IsSelected = (bool) e.NewValue;
}
}
public bool SelectAll
{
get { return (bool) GetValue(SelectAllProperty); }
set { SetValue(SelectAllProperty, value); }
}
Finally figured out how to trigger the "SelectAll" property. Notice the:
<ComboBoxItem>
<CheckBox ... />
</ComboBoxItem>
Generic.xaml
...
<ComboBox.Resources>
<ResourceDictionary>
<CollectionViewSource x:Key="items" Source="{Binding ItemsSource, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
</ResourceDictionary>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<CompositeCollection>
<ComboBoxItem>
<CheckBox Content="All" IsChecked="{Binding SelectAll, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
</ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource items}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
...
CheckComboBox.cs
public class CheckComboBox : ComboBox
{
public class CheckComboBoxItem : ModelBase
{
public CheckComboBoxItem(bool isSelected, string text)
{
IsSelected = isSelected;
Text = text;
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { Set(() => IsSelected, ref _isSelected, value); }
}
private string _text;
public string Text
{
get { return _text; }
set { Set(() => Text, ref _text, value); }
}
}
static CheckComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckComboBox), new FrameworkPropertyMetadata(typeof(CheckComboBox)));
}
public static readonly DependencyProperty SelectAllProperty =
DependencyProperty.Register("SelectAll",
typeof (bool),
typeof (CheckComboBox),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnSelectAll)));
private static void OnSelectAll(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CheckComboBox checkComboBox = (CheckComboBox)d;
IEnumerable<CheckComboBoxItem> items = (IEnumerable<CheckComboBoxItem>) checkComboBox.ItemsSource;
foreach (var item in items)
{
item.IsSelected = (bool) e.NewValue;
}
}
public bool SelectAll
{
get { return (bool) GetValue(SelectAllProperty); }
set { SetValue(SelectAllProperty, value); }
}
}
Now I just have to figure out how to automatically de-select the "Select All" check box when another item is de-selected.

Resources