WPF - Selector - Custom Control - SelectionChanged Event Not Firing - wpf

I am new to custom control creation. I have laid some groundwork for a new custom control based on the Selector class. My understanding was that I should use this class since I needed the control to have an Items collection and the ability to handle selections. I believe that changing the ItemTemplate may have overriden some of this ability because I do not receive the SelectionChanged event at the control level or application level. I would think if I'm right that there is some sort of SelectionRegion XAML tag that I can put the DataTemplate innards into. I have not had luck in finding anything like this. After looking through Google for a while, I am ready to just ask. What am I missing? Below is the ItemTemplate markup. Thanks for any help. Thanks even more if you can tell me why the Text in TextBlock is enclosed in parentheses even though the data isn't.
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
At the request of a commenter, here is the complete XAML for the control so far:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SourceMedicalWPFCustomControlLibrary">
<Style TargetType="{x:Type local:MultiStateSelectionGrid}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding Code}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50" Padding="2" ToolTip="{Binding Description}"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MultiStateSelectionGrid}">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Content="{TemplateBinding Content}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And the anemic code-behind as well:
namespace SourceMedicalWPFCustomControlLibrary
{
public class MultiStateSelectionGridState
{
public Brush Background { get; set; }
public Brush Foreground { get; set; }
public Brush Border { get; set; }
public string Text { get; set; }
public MultiStateSelectionGridState()
{
Background = Brushes.White;
Foreground = Brushes.Black;
Border = Brushes.Black;
Text = String.Empty;
}
};
public class MultiStateSelectionGrid : Selector
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty StatesProperty =
DependencyProperty.Register("States", typeof(List<MultiStateSelectionGridState>), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(new List<MultiStateSelectionGridState>(),
FrameworkPropertyMetadataOptions.AffectsRender));
public List<MultiStateSelectionGridState> States
{
get { return (List<MultiStateSelectionGridState>)GetValue(StatesProperty); }
set { SetValue(StatesProperty, value); }
}
static MultiStateSelectionGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStateSelectionGrid), new FrameworkPropertyMetadata(typeof(MultiStateSelectionGrid)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += new SelectionChangedEventHandler(MultiStateSelectionGrid_SelectionChanged);
}
void MultiStateSelectionGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Hi");
}
}
}

here is what I do. I use the apply template function of the custom control and add a handlerto the selection chnaged event of the control I want.
simple sample here:
public event EventHandler<SelectionChangedEventArgs> YourControlSelectionChanged;
private void Selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListSelectionChanged != null) {
ListSelectionChanged(sender, e);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//find or declare your control here, the x:name in xaml should be YourControl
YourControl== this.Template.FindName("YourControl", this) as YourControlType
YourControl.SelectionChanged += ResultListBox_SelectionChanged;
}
you can then bind to the name of the public event (YourControlSelectionChanged) you declared in your custom control class in xaml.
hope this helps.

From reading some full code examples of different controls, I believe my answer is that I am doing this all wrong. Instead, I need to have control that has a Selector like a ListBox in the ControlTemplate. THEN, #JKing 's advice would help me get to where I need to be. The answer to the actual question asked though is the aforementioned change from using Selector as a base class to having a selector in the template for the control. Thanks for the help.

Related

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.

Send Value from View Model to UserControl Dependency Property WPF

I have a dependency property in a UserControl with a property called SelectedColor. From my main app, the view of the window that uses this my code is:
<controls:ColorPicker SelectedColor="{Binding MyCanvas.CanvasBackgroundColor}" />
And the code from the view model is:
public MyCanvas { get; set; }
public MyWindowViewModel(MyCanvas myCanvas)
{
MyCanvas = myCanvas;
}
And then the XAML for my UserControl is:
<UserControl . . .>
<Button Click="Button_Click">
<Button.Style>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{Binding SelectedColor}" BorderBrush="Black" BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</UserControl>
And the code-behind:
public ColorPicker()
{
InitializeComponent();
DataContext = this;
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));
I think the problem might be with the line in the code-behind DataContext = this;. Is it correct that declaring this creates an entirely new context for the instance of this user control in the main app and therefore any values sent to it from the view model would be re-initialized? If so, how can I send the value over without it being re-declared? I also need the DataContext = this line because without it some functionality within my UserControl will no longer work.
Has anyone encountered this before?
Thanks in advance!
DataContext = this sets the DataContext of the UserControl to itself. You don't want to do this. Instead you could bind to a property of the UserControl using a {RelativeSource} without setting the DataContext property:
<Border Background="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
BorderBrush="Black" BorderThickness="1" />
Code-behind:
public ColorPicker()
{
InitializeComponent();
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));

Set focus to content of ContentPresenter

I need to set focus to the content of a ContentPresenter. I can assume the ContentTemplate contains an IInputElement but not anything else about it.
Here is a much simplified example that illustrates the problem:
Main window:
<Window x:Class="FiedControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:Esatto.Wpf.CustomControls;assembly=Esatto.Wpf.CustomControls"
xmlns:local="clr-namespace:FiedControlTest">
<Window.Resources>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Margin" Value="5"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background" Value="LightBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=Options}" Name="cbOptions" DisplayMemberPath="Description"/>
<Button Content="Set focus" Click="SetFocus"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="TextBox:"/>
<TextBox Name="tbText" Text="A bare text box."/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="ContentPresenter:"/>
<ContentPresenter Content="TextBox in a ContentPresenter" Name="cpText">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True"/>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</StackPanel>
</StackPanel>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = this;
Options = new ObservableCollection<Option>(new[]{
new Option(){TargetType=typeof(TextBox), Description="Bare Text Box"},
new Option(){TargetType=typeof(ContentPresenter), Description="Content Presenter"}
});
InitializeComponent();
cbOptions.SelectedIndex = 0;
}
private void SetFocus(object sender, RoutedEventArgs e)
{
var opt = cbOptions.SelectedItem as Option;
if (opt.TargetType == typeof(TextBox))
tbText.Focus();
if (opt.TargetType == typeof(ContentPresenter))
cpText.Focus();
}
public ObservableCollection<Option> Options { get; set; }
public class Option
{
public Type TargetType { get; set; }
public string Description { get; set; }
}
}
There's not much there. The bare TextBox takes focus as expected; the TextBox presented by the ContentPresenter does not.
I have tried adding Focusable="True" to the ContentPresenter but it doesn't have any visible effect. I've tried doing using Keyboard.SetFocus instead of UIElement.Focus but the behavior doesn't change.
How is this done?
In fact what you set focus is the ContentPresenter, not the inner TextBox. So you can use VisualTreeHelper to find the child visual element (the TextBox in this case) and set focus for it. However with IsReadOnly being true, you won't see any caret blinking (which may also be what you want). To show it in readonly mode, we can just set IsReadOnlyCaretVisible to true:
private void SetFocus(object sender, RoutedEventArgs e)
{
var opt = cbOptions.SelectedItem as Option;
if (opt.TargetType == typeof(TextBox))
tbText.Focus();
if (opt.TargetType == typeof(ContentPresenter)) {
var child = VisualTreeHelper.GetChild(cpText, 0) as TextBox;
if(child != null) child.Focus();
}
}
Here the edited XAML code with IsReadOnlyCaretVisible added:
<TextBox Text="{Binding Mode=OneWay}" IsReadOnly="True"
IsReadOnlyCaretVisible="True"/>
Note that the above code can only be applied in your specific case where you use a TextBox as the root visual of ContentTemplate of a ContentPresenter. In general case, you will need some recursive method to find the child visual (based on VisualTreeHelper), you can search more for this, I don't want to include it here because it's a very well-known problem/code in WPF to find visual child in WPF. (the italic phrase can be used as keywords to search for more).

WPF TargetUpdated event not firing on ContentControl

I'm using an attached property to subscrive to the TargetUpdated event from a TextBlock, so I can be notified every time the text changes.
Using the following XAML:
<DataTemplate DataType="{x:Type targetUpdatedApp:Item}">
<targetUpdatedApp:LabelControl Text="{Binding Text, NotifyOnTargetUpdated=True}" Style="{StaticResource LabelTemplateStyle}"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Items}">
<!--<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text, NotifyOnTargetUpdated=True}" targetUpdatedApp:DesiredWidth.DesiredMinWidth="120"/>
</DataTemplate>
</ListBox.ItemTemplate>-->
</ListBox>
<Button Click="ButtonBase_OnClick">Button</Button>
</StackPanel>
Here is my AttachedProperty code:
public class DesiredWidth
{
public static readonly DependencyProperty DesiredMinWidthProperty =
DependencyProperty.RegisterAttached(
"DesiredMinWidth", typeof (double),
typeof (TextBlock), new PropertyMetadata(OnDesiredMinWidthChanged));
public static double GetDesiredMinWidth(DependencyObject obj)
{
return (double) obj.GetValue(DesiredMinWidthProperty);
}
public static void SetDesiredMinWidth(DependencyObject obj, double value)
{
obj.SetValue(DesiredMinWidthProperty, value);
}
static void OnDesiredMinWidthChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var textBlock = obj as TextBlock;
if (textBlock == null)
{
return;
}
if (args.NewValue != null)
{
textBlock.TargetUpdated += OnTextBoxTargetUpdated;
}
}
static void OnTextBoxTargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
if (e.Property == TextBlock.TextProperty)
{
}
}
}
The Items collection binded to the ListBox is an ObservableCollection and the items on it implement INotifyPropertyChanged.
If I uncomment the code of the ListBox.ItemTemplate and use it instead of the style it works ok, but I use LabelControl (which basically has a Text DependencyProperty) described on the style the TargetUpdated event subscribed on the AttachedProperty never gets fired.
Could someone give me some help on this issue?
Thanks in advance.
PS: Added from comment :
<Style x:Key="LabelTemplateStyle" TargetType="{x:Type argetUpdatedApp:LabelControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type targetUpdatedApp:LabelControl}">
<TextBlock Text="{TemplateBinding Text}" TargetUpdatedApp:DesiredWidth.DesiredMinWidth="120"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
NotifyOnTargetUpdated will only apply to the binding that it's set on, not bindings on elements contained within. You have this set on the LabelControl, but the event handler is attached to the TextBlock inside its ControlTemplate, which isn't notifying. Use:
<ControlTemplate TargetType="{x:Type targetUpdatedApp:LabelControl}">
<TextBlock Text="{TemplateBinding Text, NotifyOnTargetUpdated=True}" targetUpdatedApp:DesiredWidth.DesiredMinWidth="120"/>
</ControlTemplate>
Another way you could do this, which wouldn't require you to edit bindings, is to use another attached property instead of the element's own Text property:
<TextBlock DesiredWidth.Text="{Binding Text}" DesiredWidth.MinWidth="120" />
And in DesiredWidth you could then add a OnTextChanged callback that passes the value on to the TextBlock's Text and does any other handling needed.

How to handle attached properties events?

I created an expander style that contains a checkbox in its header. The checkbox state is bound to an attached property:
<Style TargetType="{x:Type Expander}" x:Key="MyCheckboxExpander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
(...)
<CheckBox x:Name="ExpanderHeaderChk" VerticalAlignment="Center" Margin="4,0,0,2"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:AP.IsChecked)}" />
(...)
I my view, inside the expander I have a stackpanel with a ComboBox.
Whenever the user checks the expander's checkbox, I wan't that the combobox gets the first item selected, on the oher hand whenever the user unchecks it, I wan't that the selecteditem of the combobox be null.
How can I accomplish this? I'm following the MVVM pattern, but since this is more a matter of the view, I'm open to code-behind suggestions.
Well, I think your design is not optimal. You see, you are trying to change the semantics of the Expander. The real expander doesn't have the semantics with additional checkbox, so the control you are creating is not an Expander any more.
I would suggest that you switch to a user control (or maybe a custom control, look at your semantics), and expose the needed event in your control's class. The XAML for the user control should be perhaps an expander with a checkbox.
Edit: example with UserControl (not tested)
(XAML)
<UserControl x:Class="namespace:MyCheckboxExpander">
<Expander>
...
<Checkbox x:Name="cb"/>
...
</Expander>
</UserControl>
(code-behind)
public class MyCheckboxExpander : UserControl
{
MyCheckboxExpander()
{
InitializeComponent();
cb.Check += OnCheck;
}
void OnCheck(object sender, whatever2 args)
{
if (CheckboxTriggered != null)
CheckboxTriggered(new EventArgs<whatever>);
}
public event EventArgs<whatever> CheckboxTriggered;
}
WPF is so powerfull framework, that you can solve you problem just using next style for Expander:
<Style x:Key="myExpanderStyle" TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<CheckBox x:Name="PART_CheckBox" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding Content}" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="PART_ComboBox" Property="SelectedIndex" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
SAMPLE:
<Expander Style="{StaticResource myExpanderStyle}">
<x:Array Type="sys:String">
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</x:Array>
</Expander>
Just XAML! I like XAML declarativity.
But from MVVM perspective, this approach has one disadvantage - I can't cover this case with unit tests. So, I would prefer:
create view model with properties: IsChecked(bound to CheckBox),
SelectedItem(bound to ComboBox) and Source(ItemsSource for ComboBox) -
abstration of my real view without any references on controls;
write a logic in view model that set or unset SelectedItem depending
on IsChecked property;
cover that logic with unit test (yep, you can
even start with this point, if you like test first approach).
I followed the suggestion provided by #Baboon and I created a custom control with a routed event named CheckedChanged, this way I can access it through the view's xaml and code-behind:
[TemplatePart(Name = "PART_Expander", Type = typeof(Expander))]
[TemplatePart(Name = "PART_CheckBox", Type = typeof(CheckBox))]
public class MyCustomExpander : Expander
{
static MyCustomExpander()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomExpander), new FrameworkPropertyMetadata(typeof(MyCustomExpander)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(MyCustomExpander),
new UIPropertyMetadata(false));
#region Events
private CheckBox chkExpander = new CheckBox();
public CheckBox ChkExpander { get { return chkExpander; } private set { chkExpander = value; } }
public static readonly RoutedEvent CheckedChangedEvent = EventManager.RegisterRoutedEvent("ExtraButtonClick",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyCustomExpander));
public event RoutedEventHandler CheckedChanged
{
add { AddHandler(CheckedChangedEvent, value); }
remove { RemoveHandler(CheckedChangedEvent, value); }
}
void OnCheckedChanged(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(CheckedChangedEvent, this));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckBox chk = base.GetTemplateChild("PART_CheckBox") as CheckBox;
if (chk != null)
{
chk.Checked += new RoutedEventHandler(OnCheckedChanged);
chk.Unchecked += new RoutedEventHandler(OnCheckedChanged);
}
}
#endregion
}
I want to thank to #Baboon and #Vlad for their help.

Resources