I have an issue with ListBoxItems. I am trying to make all controls in the ListBoxItem select it as well, so clicking on a TextBox, Label, etc will select the ListBoxItem. Pretty simple so far.
I am also changing the ListBoxItem Template to change the selection visualization from highlighting the background to just drawing a border. Also pretty simple.
The combination of these two, however, seems to cause some really irritating issues with MouseDown and PreviewMouseDown, specifically in my case regarding Labels in a Grid, where one creates a "void" occupied by Grid space.
Using snoop, I can see the PreviewMouseDown event stopping at the ScrollViewer inside the ListBox, and not going all the way to the ListBoxItem.
XAML:
<Window x:Class="ListBoxClickThroughTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="525"
Height="350">
<Grid>
<ListBox ItemsSource="{Binding Items}"
SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Name="VerySuperLongLabel"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Left"
Content="VerySuperLongLabel"
Padding="0" />
<TextBox Name="Textbox1"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox1 Text" />
<Label Name="ShortLabel"
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Left"
Content="ShortLabel"
Padding="0" />
<TextBox Name="Textbox2"
Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Right"
Text="Textbox2 Text" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<EventSetter Event="MouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd"
BorderThickness="1">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Property="BorderBrush" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
Code-behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ListBoxClickThroughTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
Items = new List<string>() { "1", "2" };
InitializeComponent();
DataContext = this;
}
public List<string> Items { get; set; }
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var listBoxItem = (ListBoxItem)sender;
listBoxItem.IsSelected = true;
}
}
}
However, if I remove the Template setter, all is well. Is there some magic in the template I'm missing? I tried renaming the border to "Bd" as that was what the default template border was named, but no luck. Any ideas?
If you change the horizontal alignment of the labels from "Left" to "Stretch" this will fix the problem and keep the visual formatting the same.
Mousedown events only work in areas where elements exist. By having the labels at a "left" horizontal alignment, you are creating the "void" you mentioned, where no element exists at that level that can be clicked. To visually see the difference, try temporarily setting the background property of the label elements that are giving you problems, and you'll see the element doesn't extend all the way to the textbox.
Related
I have a template for a DataGrid located in a ResourceDictionary.
BlockStyles.xaml
<Style TargetType="DataGrid" x:Key="SearchExpGrid">
<Setter Property="AlternatingRowBackground" Value="#4C87C6ff"/>
<Setter Property="GridLinesVisibility" Value="None"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="CellStyle">
<Setter.Value ... />
</Setter>
<Setter Property="RowStyle">
<Setter.Value ... />
</Setter>
<Setter Property="ColumnHeaderStyle">
<Setter.Value>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border
x:Name="Border" Background="White" BorderBrush="#4C87C6" BorderThickness="1" >
<StackPanel Orientation="Horizontal" Margin="5,5" HorizontalAlignment="Center">
<TextBlock
x:Name="TxtB" Text="{Binding}"
Foreground="#4C87C6" FontWeight="DemiBold" HorizontalAlignment="Center"/>
<Image Source="../Images/dropdown.png"
Width="10" Height="10" Margin="5,0,5,0"
MouseEnter="DropdownButton_MouseEnter"
MouseLeave="DropdownButton_MouseLeave"
MouseEnter="DropdownButton_Click"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#4C87C6"/>
<Setter TargetName="TxtB" Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
I always have an error because the function can't find it's definition.
I first implemented the function in the View file where the style is used but it doesn't work.
I tried this method from StackOverflow using a resource class inheriting ResourceDictionnary but got the same error.
I then tried to use ICommand and RelayCommand to execute the function from the ViewModel but didn't got any result.
I also didn't find where I could add an EventHandler ImgDropdownButton.MouseEnter += new MouseEventHandler(MouseEnter_DropdownButton); using MVVM.
Is there a better solution for this kind of behaviour or if adding an EventHandler is the best solution, where sould be the best place to add it ?
Thanks in advance
Edit :
I managed to handle the function using a code-behind file for my ResourceDictionary following this.
BlockStyles.xaml.cs
private void DropdownButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Mouse.OverrideCursor = Cursors.Hand;
}
private void DropdownButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
Mouse.OverrideCursor = Cursors.Arrow;
}
private void DropdownButton_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
//Function to show the popup
}
The MouseEnter and MouseLeave function are working, but I don't understand how to use the function to make my popup appear.
What I'm trying to do is that when I click on the Dropdown Image on the column header, I want to display a Popup, like an Excel one. This will allow the user to filter the columns values.
The file where my Grid and Popup are : (SearchExpView.xaml)
<Grid Grid.Row="2" Grid.Column="1">
<searchcomponents:ExpListView x:Name="ExpDatagrid"
DataContext="{Binding OExpListVM}"
Width="auto" Height="auto"/>
</Grid>
<Popup x:Name="PopupFiltre">
PopupFiltre content
</Popup>
Definition of my Datagrid : (ExpListView.xaml)
<Grid>
<DataGrid x:Name="ExpGrid" Style="{StaticResource SearchExpGrid}"
BorderThickness="0" BorderBrush="#4C87C6"
HorizontalAlignment="Left" VerticalAlignment="Top"
MinHeight="200" Height="auto" Margin="10,10,0,0"
MinWidth="780" Width="auto"
ItemsSource="{Binding}" DataContext="{Binding tableExpertise.DefaultView}"
AutoGenerateColumns="True" CanUserAddRows="False" IsReadOnly="True">
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding DataContext.OnRowDoubleClickedCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding ElementName=ExpGrid, Path=CurrentItem}"/>
</DataGrid.InputBindings>
<DataGrid.ContextMenu>
<ContextMenu Name="dgctxmenu">
<Separator></Separator>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
I'm looking for a way to be able to implement this popup fonction but I can't find out how to link everything together.
My Window is SearchExpView.xaml (with the Datagrid and the Popup). My Datagrid component is defined in ExpListView.xaml and styled in BlockStyles.xaml, which is not a window. I want to make the Popup (in SearchExpView.xaml) visible by clicking on the dropdown button (defined in BlockStyles.xaml)
Then you need to get a reference to the Popup in the window from the ResourceDictionary somehow.
You could for example use the static Application.Current.Windows property:
var window = Application.Current.Windows.OfType<SearchExpView>().FirstOrDefault();
if (window != null)
window.PopupFiltre.IsOpen = true;
Also make sure that you make the Popup accessible from outside the SearchExpView class:
<Popup x:Name="PopupFiltre" x:FieldModifier="internal">
...
I am working on a project in C# WPF. I have a tab container and I want to dynamically load different types of tabs into the tab container as the user requires. As an example I am doing something like the following:
tabContainer.Items.Add(new MyUserControl());
I want each tab to have a close button so the tab can be removed the container when the user no longer requires it.
I found this code project example but from what I can see you are a loading a user control which contains the xaml for the tab itself, not the tab content or am I missing something.
How can I load in my User Control into the tab container, but also have the tab closable.
Currently the tab that I am loading in uses some static binding to set the tab title using the following:
<TabControl x:Name="tabContainer" Grid.Column="2" Margin="10,45,0,0" RenderTransformOrigin="0.5,0.55" Grid.ColumnSpan="3">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
My user control then has a `public string TabHeader{get;set;} which gets set in the constructor depending on what constructor of my user control is used.
You will have to define the close Button yourself. You could for example do this in the HeaderTemplate of the TabItem:
<TabControl x:Name="tabContainer">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Content="x" Click="Button_Click_2"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TabItem}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The Tag property is bound to the UserControl in the Items collection which you can remove in the click event handler of the Button, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
tabContainer.Items.Add(new MyUserControl());
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
tabContainer.Items.Remove(button.Tag);
}
}
If you want to add a close button to each tab, that would be in the TabItem style ControlTemplate. Normally you'd specify the data context (i.e. the data only that's driving the content) in Content and then specify the look in ContentTemplate. If your Content is a UserControl then you don't specify the ContentTemplate since a UserControl knows how to draw itself.
For my sins, I've added close-tab buttons to the WPF TabControl. I ended up putting the close button in the ItemTemplate. Here's a minimal version that works with the way you're populating the TabControl and the header content:
<TabControl
>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Grid.Column="0"
/>
<Button
VerticalAlignment="Center"
Grid.Column="1">
<Path
Data="M 0, 0 L 12, 12 M 12,0 L 0,12"
Stroke="Red"
StrokeThickness="2"
Width="12"
Height="12"
/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<local:UserControl1 TabHeader="First Item" />
<local:UserControl1 TabHeader="Second Item" />
</TabControl>
I've created a user control which uses the avalonedit control. The XAML for the user control is:
<UserControl x:Class="CodeNote.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:editing="clr-namespace:ICSharpCode.AvalonEdit.Editing">
<UserControl.Resources>
<ControlTemplate x:Key="TextBoxBaseControlTemplate1" TargetType="{x:Type TextBoxBase}">
<Border Background="{TemplateBinding Background}"
x:Name="Bd" BorderBrush="#D1D1E8"
BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="10, 10, 0, 0">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" TargetName="Bd"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="Width" Value="Auto">
<Setter Property="MinWidth" Value="100"/>
</Trigger>
<Trigger Property="Height" Value="Auto">
<Setter Property="MinHeight" Value="20"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Margin="0,0,0,10">
<TextBox x:Name="txtTitle" VerticalContentAlignment="Center" Template="{StaticResource TextBoxBaseControlTemplate1}" FontWeight="Bold" Margin="5,5,5,0" Padding="5, 3, 5, 2" FontFamily="Arial" FontSize="12" BorderThickness="1,1,1,0" Background="#FFF0F0F0"></TextBox>
<Border BorderThickness="1" CornerRadius="0,0,10,10" BorderBrush="#D1D1E8" Background="#FFF7F7F9" Margin="5,0,5,0" Padding="5,5,5,5">
<avalonEdit:TextEditor
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Name="textEditor"
FontFamily="Courier New"
SyntaxHighlighting="Java"
Background="#FFF7F7F9"
ShowLineNumbers="True"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility ="Hidden"
WordWrap="True"
FontSize="12pt"/>
</Border>
</StackPanel>
</Grid>
</UserControl>
The main window contains the following StackPanel within a ScrollViewer within a Grid
<Window x:Class="CodeNote.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:editing="clr-namespace:ICSharpCode.AvalonEdit.Editing"
x:Name="mainWin"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Visible" CanContentScroll="False">
<StackPanel Grid.Row="0" Margin="10,10,10,0" VerticalAlignment="Top" x:Name="container">
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
The user should be able to programmatically add the usercontrol to the main window, which is accomplished in the main window code behind with:
UserControl1 avEditor = new UserControl1(); container.Children.Add(avEditor);
My problem is that the scrollviewer does not scroll to the bottom when the avalonedit control's contents becomes too vertically large for the window. The insertion point simply disappears off the bottom of the visible window and the scroll position stays at the top.
I notice that the scroll works correctly if I add a regular textbox instead of a avalonedit control.
How can I rectify this (I am very new to WPF)
Please note that the program needs to be able to add multiple text input controls to this scroll viewer, e.g avalonedit followed by a text box followed by another textbox followed by another avalonedit. Therefore, I can't just use the scrollviewer's ScrollToBottom method because the control being edited might not be the last control in the scrollviewer.
I simply need the insertion point to remain onscreen and the window to scroll accordingly.
Seems like Caret.BringCaretToView() only scrolls AvalonEdit itself, it doesn't send BringIntoView request up the visual tree.
I think you'd need to change the AvalonEdit source code to fix this.
I am no expert but below is what I use for a pseudo tail using AvalonEdit. The _scroll is just a checkbox the user can uncheck to stop scrolling.
private void PullAndLoadData()
{
Task.Delay(1000);
TailFile = TailFile + _logStream.Read();
Dispatcher.Invoke(() => { textEditor.Text = TailFile; });
if (_scroll) Dispatcher.Invoke(() => { textEditor.ScrollToEnd(); });
}
In the WPF XAML code below, if I am in the SelectTaskItemClick event for the templated Button, how do I get the ListBoxItem ItemSource object that is currently selected?
<!-- ListBox ITEMS -->
<TaskDash:ListBoxWithAddRemove x:Name="listBoxItems" Grid.Row="1" Grid.Column="3" Grid.RowSpan="3"
ItemsSource="{Binding}">
<!--ItemsSource="{Binding}" DisplayMemberPath="Description">-->
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=Selected}"/>
</Style>
<TaskDash:ListBoxWithAddRemove.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Left" Click="SelectTaskItemClick">SELECT</Button>
<TextBox DockPanel.Dock="Left" Name="EditableDescription" Text="{Binding Description}" Height="25" Width="100" />
<Button DockPanel.Dock="Left" Click="EditTaskItemClick">EDIT</Button>
</DockPanel>
</DataTemplate>
</TaskDash:ListBoxWithAddRemove.ItemTemplate>
</TaskDash:ListBoxWithAddRemove>
If I try to get the Parent or TemplateParent, it gives me the ContentPresenter or Style or something similar.
private void SelectTaskItemClick(object sender, RoutedEventArgs e)
{
Button taskItemButton = (Button) e.OriginalSource;
ContentPresenter taskItem = (ContentPresenter) taskItemButton.TemplatedParent;
taskItem = (ContentPresenter)taskItemButton.TemplatedParent;
Style taskItem2 = taskItem.TemplatedParent;
taskItem2 = taskItem.TemplatedParent;
DependencyObject taskItem3 = taskItem2.Parent;
//DependencyObject taskItem3 = taskItem2.TemplatedParent;
//TaskItem taskItemObj = taskItem2;
}
In the code above, I'm guessing it is grabbing that from App.XAML where that custom ListBoxWithAddRemove control is defined. How do I traverse the actual form's XAML instead [the first code shown above]?
<Style x:Key="{x:Type TaskDash:ListBoxWithAddRemove}" TargetType="{x:Type TaskDash:ListBoxWithAddRemove}">
<Setter Property="Margin" Value="3" />
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="MinWidth" Value="120"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TaskDash:ListBoxWithAddRemove}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Grid.Row="0"
Click="DeleteControlClick">Delete</Button>
<Button Grid.Column="1" Grid.Row="0"
Click="AddControlClick">Add</Button>
<Border
Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"
Name="Border"
Background="{StaticResource WindowBackgroundBrush}"
BorderBrush="{StaticResource SolidBorderBrush}"
BorderThickness="1"
CornerRadius="2">
<ScrollViewer
Margin="0"
Focusable="false">
<StackPanel Margin="0" IsItemsHost="True" />
</ScrollViewer>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can use the VisualTreeHelper to walk up the tree and stop if you have an object of the right type, e.g.
private void SelectTaskItemClick(object sender, RoutedEventArgs e)
{
var b = sender as Button;
DependencyObject item = b;
while (item is ListBoxItem == false)
{
item = VisualTreeHelper.GetParent(item);
}
var lbi = (ListBoxItem)item;
//...
}
(If you just want to select the item that can (and should) just be done via the established binding, e.g.)
private void SelectTaskItemClick(object sender, RoutedEventArgs e)
{
// The DataContext should be an item of your class that should
// have a Selected property as you bind to it in a style.
var data = (sender as FrameworkElement).DataContext as MyClass;
data.Selected = true;
}
Assuming that
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=Selected}"/>
</Style>
works the way that it appears you intended, you should be able to loop through the items in your DataContext used as the ItemsSource for your listbox and check the Selected property of each to find the one currently selected. The more typical way of determining the selected item from a ListBox is by using listBox.SelectedItem where listBox is a variable that refers to the ListBox in question. Alternatively you might be able to access it off the sender parameter to the SelectTaskItemClick method. Another method you might try is helper methods to traverse the visual tree such as described at The Coding Bloke, and The Code Project - LINQ to Visual Tree.
I have a custom Expander control called SpecialExpander. It is basically just a standard Expander with a fancy header and a couple properties (HeaderText and IsMarkedRead).
I began by creating a simple class:
public class SpecialExpander : Expander
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
Then I created a style that sets a couple properties on the expander (e.g., margins, padding, etc.) and, importantly, it also defines a custom DataTemplate for the HeaderTemplate property. The template is basically a grid with two rows.
As shown in the illustrations below...
for the top row, I'd like a fixed layout (it's always TextBlock TextBlock CheckBox)
for the bottom row, however, I want to be able to provide custom XAML for each expander.
I tried putting <ContentControl Grid.Row="1" ... /> in the DataTemplate, but I couldn't figure out how to hook it up properly.
alt text http://img85.imageshack.us/img85/1194/contentcontrolwithintem.jpg
Question
How can I build a DataTemplate for my SpecialExpander so that the header has some fixed content (top row) and a place-holder for custom content (bottom row)?
For the second illustration, I would want to be able to do something like this:
<SpecialExpander HeaderText="<Expander Header Text>" IsMarkedRead="True">
<SpecialExpander.Header>
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Med" />
<RadioButton Content="Low" />
</StackPanel>
<SpecialExpander.Header>
<Grid>
<Label>Main Content Goes Here</Label>
</Grid>
</SpecialExpander>
It hit me this morning how to solve this: instead of building a SpecialExpander, I just need a normal Expander. Then, for the header, I will use a custom ContentControl called SpecialExpanderHeader.
Here's how it works...
SpecialExpanderHeader class:
public class SpecialExpanderHeader : ContentControl
{
public string HeaderText { get; set; }
public bool IsMarkedRead { get; set; }
}
SpecialExpanderHeader style:
<Style TargetType="custom:SpecialExpanderHeader">
<Setter Property="Padding" Value="10" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="custom:SpecialExpanderHeader">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=HeaderText}" />
<CheckBox Margin="100,0,0,0" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=custom:SpecialExpanderHeader}, Path=IsMarkedRead}" />
</StackPanel>
<Separator Grid.Row="1" />
<ContentPresenter Grid.Row="2" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Expander style
<Style x:Key="Local_ExpanderStyle" TargetType="Expander" BasedOn="{StaticResource {x:Type Expander}}">
<Setter Property="Margin" Value="0,0,0,10" />
<Setter Property="Padding" Value="10" />
<Setter Property="FontSize" Value="12" />
</Style>
Usage
<Expander Style="{StaticResource Local_ExpanderStyle}">
<Expander.Header>
<custom:SpecialExpanderHeader IsMarkedRead="True" HeaderText="Test">
<StackPanel Orientation="Horizontal">
<RadioButton Content="High" />
<RadioButton Content="Medium" />
<RadioButton Content="Low" />
</StackPanel>
</custom:SpecialExpanderHeader>
</Expander.Header>
<Grid>
<!-- main expander content goes here -->
</Grid>
</Expander>