WPF Datagrid binding custom column headers - wpf

I am trying to figure out how to bind a WPF DataGrid's column header and main data to a data source using an MVVM pattern. The result I'm looking for would look like this:
(source: vallelunga.com)
I've successfully styled the headers here, but I'm unsure how to bind the values in the headers. Specifically, the IsChecked property of the check-box, the selected index of the combo box and the value of the text box.
I was previously using a simple DataTable to populate the main grid data, but I'm going to need something more complex to hold both the grid data and the values for each column. Or perhaps I can store them as separate entities entirely.
So, does anyone have any idea of how I might pull off this binding? One limitation is that the columns must be auto-generated since I have no idea what they will be until runtime. The application simply loads the data form an Excel spreadsheet and there may be any number of columns present.
Thanks,
Brian

Here's what I ended up doing to use this with the MVVM pattern:
I have two sets of data for binding on my view model: one for the actual grid data and one for the column headers. Currently these are exposed as two properties:
// INotifyPropertyChanged support not shown for brevity
public DataTable GridData { get; set; }
public BindingList<ImportColumnInfo> ColumnData { get; set; }
The trick to working with two differing sets of data is in the grid. I have subclassed the DataGrid and given the grid an additional data source called ColumnSource, as a dependency property. This is what is bound to the ColumnData on my view model. I then set the header of each auto-generated column to the appropriately indexed data in the ColumnSource data source. The code is as follows:
public class ImporterDataGrid : DataGrid
{
protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
{
base.OnAutoGeneratingColumn(e);
int columnIndex = this.Columns.Count;
var column = new ImporterDataGridColumn();
column.Header = ColumnSource[columnIndex];
column.Binding = new Binding(e.PropertyName) { Mode = BindingMode.OneWay };
e.Column = column;
}
public IList ColumnSource
{
get { return (IList)GetValue(ColumnSourceProperty); }
set { SetValue(ColumnSourceProperty, value); }
}
public static readonly DependencyProperty ColumnSourceProperty = DependencyProperty.Register("ColumnSource", typeof(IList), typeof(ImporterDataGrid), new FrameworkPropertyMetadata(null));
}
I can now perform normal data binding in the templated header of my columns, which will all bind against the data in the ColumnData property of my view model.
UPDATE: I was asked to show the XAML for my grid. It's really basic, but here it is:
<Controls:ImporterDataGrid
AutoGenerateColumns="True" x:Name="previewDataGrid"
VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Visible"
IsReadOnly="True"
SelectionMode="Extended"
HeadersVisibility="Column"
ItemsSource="{Binding PreviewData}"
ColumnSource="{Binding PreviewColumnData}"
Style="{StaticResource ImporterDataGridStyle}"
Background="White" CanUserReorderColumns="False" CanUserResizeRows="False"
CanUserSortColumns="False" AlternatingRowBackground="#FFFAFAFA" AllowDrop="True" />
And here is the ImporterColumnHeaderStyle:
<Style x:Key="ImporterDataGridColumnHeaderStyle" TargetType="{x:Type toolkit:DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type toolkit:DataGridColumnHeader}">
<Grid>
<toolkit:DataGridHeaderBorder Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" IsClickable="{TemplateBinding CanUserSort}" IsHovered="False" IsPressed="False" SortDirection="{TemplateBinding SortDirection}">
<Grid>
<CheckBox Height="16" Margin="6,6,16,0" Name="importCheckBox" IsChecked="{Binding Path=Import}" VerticalAlignment="Top">Import Column</CheckBox>
<StackPanel IsEnabled="{Binding Path=Import}">
<ComboBox Height="24" Margin="6,29,6,0" Name="columnTypeComboBox" VerticalAlignment="Top" SelectedValue="{Binding ColumnType}" ItemsSource="{Binding Source={local:EnumList {x:Type Models:ImportColumnType}}}">
</ComboBox>
<TextBox Height="23" Margin="6,6,6,33" Name="customHeadingTextBox" VerticalAlignment="Bottom" Text="{Binding Path=CustomColumnName}" IsEnabled="{Binding ColumnType, Converter={StaticResource ColumnTypeToBooleanConverter}}" />
</StackPanel>
<TextBlock Height="20" Margin="6,0,6,7" Name="originalHeadingTextBlock" Text="{Binding Path=OriginalColumnName}" VerticalAlignment="Bottom" Foreground="Gray" />
</Grid>
</toolkit:DataGridHeaderBorder>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="Width" Value="8"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeWE"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Setter Property="Width" Value="8"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Cursor" Value="SizeWE"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

I am definitely a WPF / MVVM / databinding noob, but have been working hard on this stuff lately. I don't know what you have wired up so far, but first you'll want to set the DataContext for your View. Since you're using MVVM, I assume you have a ViewModel, so that should be the DataContext for your View.
i.e. if you have your View create / own your ViewModel, it could look something like this:
MyViewModel vm = new MyViewModel();
this.DataContext = vm;
You can easily databind your CheckBox, ComboBox, and TextBox to properties in your ViewModel. I have found the easiest way is to make your ViewModel inherit from a base viewmodel class, like the one that Josh Smith wrote. This will give you a method to call internally when you want the ViewModel to notify the GUI of any changes in values.
Assuming you have properties like ImportColumn, LastName, and LastNameText (all C# properties, not fields that call OnPropertyChanged accordingly), then your XAML would look something like this:
<CheckBox IsChecked="{Binding ImportColumn}" />
<ComboBox SelectedItem="{Binding LastName}" />
<TextBox Text="{Binding LastName Text, Mode=TwoWay}" />
I hope this helps you out. If not, please comment and I'll try to do as best as I can to try other things out.

We do something similar in our app.
What i have done is derived my own column type (DataGridSearchableBooleanColumn), then i replace the DataGridColumnHeader template, i put two content presenters in there. the first i bind to the content (the same as the default template) the second i bind to the column. I use a data template for the column (i have a few of them for different search types (text, combo, boolean). then i add the extra properties to the column so i can bind to them. See if this code makes sense.
<!--Style for the datagrid column headers, contains a text box for searching-->
<Style
x:Key="columnHeaderStyle"
TargetType="dg:DataGridColumnHeader">
<Setter
Property="Foreground"
Value="#FF000000" />
<Setter
Property="HorizontalContentAlignment"
Value="Left" />
<Setter
Property="VerticalContentAlignment"
Value="Center" />
<Setter
Property="IsTabStop"
Value="False" />
<Setter
Property="Padding"
Value="1,2,1,2" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="dg:DataGridColumnHeader">
<Grid
x:Name="Root">
<dg:DataGridHeaderBorder
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
IsClickable="{TemplateBinding CanUserSort}"
IsHovered="{TemplateBinding IsMouseOver}"
IsPressed="{TemplateBinding IsPressed}"
SeparatorBrush="{TemplateBinding SeparatorBrush}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
SortDirection="{TemplateBinding SortDirection}">
<Grid
HorizontalAlignment="Stretch"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.Resources>
<DataTemplate
DataType="{x:Type local:DataGridSearchableBooleanColumn}">
<CheckBox
Margin="0,5,0,0"
IsThreeState="True"
IsChecked="{Binding Path=IsChecked}" />
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition
Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition
Height="19" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay, Path=Content}" />
<Path
x:Name="SortIcon"
Fill="#FF444444"
Stretch="Uniform"
HorizontalAlignment="Left"
Margin="4,0,0,0"
VerticalAlignment="Center"
Width="8"
Opacity="0"
RenderTransformOrigin=".5,.5"
Grid.Column="1"
Data="F1 M -5.215,6.099L 5.215,6.099L 0,0L -5.215,6.099 Z ">
<Path.RenderTransform>
<ScaleTransform
ScaleX=".9"
ScaleY=".9" />
</Path.RenderTransform>
</Path>
<ContentPresenter
x:Name="columnHeaderContentPresenter"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Column}"
Grid.Row="1"
Grid.ColumnSpan="2"
Margin="0,0,0,0" />
</Grid>
</dg:DataGridHeaderBorder>
<Thumb
x:Name="PART_LeftHeaderGripper"
HorizontalAlignment="Left">
<Thumb.Style>
<Style
TargetType="{x:Type Thumb}">
<Setter
Property="Width"
Value="8" />
<Setter
Property="Background"
Value="Transparent" />
<Setter
Property="Cursor"
Value="SizeWE" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type Thumb}">
<Border
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
<Thumb
x:Name="PART_RightHeaderGripper"
HorizontalAlignment="Right">
<Thumb.Style>
<Style
TargetType="{x:Type Thumb}">
<Setter
Property="Width"
Value="8" />
<Setter
Property="Background"
Value="Transparent" />
<Setter
Property="Cursor"
Value="SizeWE" />
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type Thumb}">
<Border
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Thumb.Style>
</Thumb>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Related

GoogleImages-like listbox with detail panel

How would you design a Google images -like component?
I'm not sure how to handle the detail panel which shows under each image when selected.
The detail panel:
- shows between two rows of images.
- keep left and right items in place.
SOLUTION:
Thanks to LittleBit for providing a very nice solution to the problem. Based on his hints, i created the following general-purpose component taking care of a few details you will eventually face when making LittleBit solution production-ready.
file: DetailedList.cs
public class DetailedList : ListBox
{
#region DetailsTemplate
public DataTemplate DetailsTemplate
{
get { return (DataTemplate)GetValue( DetailsTemplateProperty ); }
set { SetValue( DetailsTemplateProperty, value ); }
}
public static readonly DependencyProperty DetailsTemplateProperty =
DependencyProperty.Register( nameof( DetailsTemplate ), typeof( DataTemplate ), typeof( DetailedList ) );
#endregion
static DetailedList()
{
Type ownerType = typeof( DetailedList );
DefaultStyleKeyProperty.OverrideMetadata( ownerType,
new FrameworkPropertyMetadata( ownerType ) );
StyleProperty.OverrideMetadata( ownerType,
new FrameworkPropertyMetadata( null, ( depObj, baseValue ) =>
{
var element = depObj as FrameworkElement;
if( element != null && baseValue == null )
baseValue = element.TryFindResource( ownerType );
return baseValue;
} ) );
}
}
file: StretchGrid.cs
internal class StretchGrid : Grid
{
private Expander _expander;
#region ParentPanel
public Panel ParentPanel
{
get { return (Panel)this.GetValue( ParentPanelProperty ); }
set { this.SetValue( ParentPanelProperty, value ); }
}
public static readonly DependencyProperty ParentPanelProperty = DependencyProperty.Register(
nameof( ParentPanel ), typeof( Panel ), typeof( StretchGrid ), new PropertyMetadata( null, ParentPanelChanged ) );
private static void ParentPanelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var stretchGrid = d as StretchGrid;
if( stretchGrid == null ) return;
if( e.NewValue is Panel panel )
panel.SizeChanged += stretchGrid.UpdateMargins;
}
#endregion
public StretchGrid()
{
this.Loaded += StretchGrid_Loaded;
}
private void StretchGrid_Loaded( object sender, RoutedEventArgs e )
{
_expander = this.FindLogicalParent<Expander>();
_expander.Expanded += UpdateMargins;
_expander.SizeChanged += UpdateMargins;
this.UpdateMargins( null, null );
}
private void UpdateMargins( object sender, RoutedEventArgs e )
{
if( ParentPanel == null ) return;
if( _expander == null ) return;
Point delta = _expander.TranslatePoint( new Point( 0d, 0d ), ParentPanel );
//Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full row under the item)
this.Margin = new Thickness( -delta.X, 0, delta.X + _expander.ActualWidth - ParentPanel.ActualWidth, 0 );
}
}
file: DetailedList.xaml
<Style x:Key="{x:Type cc:DetailedList}" TargetType="{x:Type cc:DetailedList}"
BasedOn="{StaticResource {x:Type ListBox}}">
<Style.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="NoButtonExpander.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Style.Resources>
<Setter Property="Grid.IsSharedSizeScope" Value="True"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}"
Tag="{Binding RelativeSource={RelativeSource Self}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Border" SnapsToDevicePixels="True">
<Expander Style="{StaticResource NoButtonExpander}" VerticalAlignment="Top"
IsExpanded="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}">
<Expander.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="A"/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"/>
</Grid>
</Expander.Header>
<cc:StretchGrid ParentWrapPanel="{Binding Path=Tag,
RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}">
<ContentPresenter ContentTemplate="{Binding DetailsTemplate,
RelativeSource={RelativeSource AncestorType={x:Type cc:DetailedList}}}"/>
</cc:StretchGrid>
</Expander>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
file: NoButtonExpander.xaml
<Style x:Key="ExpanderRightHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderUpHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderLeftHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter HorizontalAlignment="Center" Margin="0,4,0,0" Grid.Row="1" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Top"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderHeaderFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle Margin="0" SnapsToDevicePixels="true" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpanderDownHeaderStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Padding="{TemplateBinding Padding}">
<Grid Background="Transparent" SnapsToDevicePixels="False">
<ContentPresenter Grid.Column="1" HorizontalAlignment="Left" Margin="4,0,0,0" RecognizesAccessKey="True" SnapsToDevicePixels="True" VerticalAlignment="Center"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="NoButtonExpander" TargetType="{x:Type Expander}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="true">
<DockPanel>
<ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" Style="{StaticResource ExpanderDownHeaderStyle}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
<ContentPresenter x:Name="ExpandSite" DockPanel.Dock="Bottom" Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Right">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Right"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Left"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderRightHeaderStyle}"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Up">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Top"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Bottom"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderUpHeaderStyle}"/>
</Trigger>
<Trigger Property="ExpandDirection" Value="Left">
<Setter Property="DockPanel.Dock" TargetName="ExpandSite" Value="Left"/>
<Setter Property="DockPanel.Dock" TargetName="HeaderSite" Value="Right"/>
<Setter Property="Style" TargetName="HeaderSite" Value="{StaticResource ExpanderLeftHeaderStyle}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My approach is to use a ListView with some modifications
The ItemsPanel of the ListView must have a 'wrap' function, so i used a Wrappanel to display the Image-Previews in a field.
The ItemContainter of the ListViewItem must have two states. An Image-Preview state and a Detail-View therfore an Expander should do the trick.
The Details of an Image must be displayed across the whole Column under the Image. We need something which renders outside the Boundaries of a ListViewItem (more later)
It now looks like this
The Image Details are now messed up due to the Boundaries. The Height is no problem because it just moves the next Column down until it fits in. The Problem is the Width and its Position (its not left aligned).
I created a small CustomControl, the StretchGrid which renders its Content across the whole Column and left aligned. This StretchGrid get the relative distance to the left Boundary and sets it as negative Margin.Left to render it properly. Now it looks like this (and i hope that is exactly what you are looking for).
Now the Style of the ListView
<!-- Image List with Detail -->
<Style x:Key="PicList" TargetType="{x:Type ListView}">
<!-- Only one Picture can be selected -->
<Setter Property="SelectionMode" Value="Single"/>
<!-- Enable Multi-Line with a WrapPanel Around the Items -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (ListView.ActualWidth),RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" Tag="{Binding RelativeSource={RelativeSource Self}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Override Display area of the Item -->
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListViewItem}">
<!-- Define Image Item -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<!-- Use Expander Header as Preview/Thumbnail and display Details below when expanded -->
<Expander x:Name="PicThumbnail" Style="{StaticResource NoButtonExpander}" IsExpanded="{Binding (ListViewItem.IsSelected), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}" Width="{Binding (ListViewItem.ActualWidth), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}">
<Expander.Header>
<!-- Thumbnail/Preview Section -->
<StackPanel>
<Image Source="/XAML;component/Assets/Images/Thumb.png" Height="16" Width="16" />
<Label Content="{Binding Name}"/>
</StackPanel>
</Expander.Header>
<!-- Self stretching Grid (Custom Control) -->
<cc:StretchGrid x:Name="PicDetails" Background="LightGray" ParentWrappanel="{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WrapPanel}}}">
<!-- Picture Detail Section (quick & dirty designed, replace later) -->
<Image ClipToBounds="False" Source="/XAML;component/Assets/Images/Highres.png" Width="128" Height="128" HorizontalAlignment="Left" Margin="10,0" />
<Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Left"/>
<Rectangle Fill="Black" Height="128" Width="5" HorizontalAlignment="Right"/>
<StackPanel Margin="150,0">
<Label Content="{Binding Name}"/>
<Label Content="Description: Lorem"/>
<Label Content="Category: Ipsum"/>
<Label Content="Owner: Dolor"/>
<Label Content="Size: 5kB"/>
</StackPanel>
</cc:StretchGrid>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
<!-- Brings selected element to front, details see edit -->
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="1" />
</Trigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
Note: Its necessary to add a Reference to the StretchGrid.
And the StretchGrid Custom Control
class StretchGrid : Grid
{
//Reference for parent expander (used to calculate Grid Margin)
private Expander m_expander;
//Property for relativesource Binding to Wrappanel in Style
public WrapPanel ParentWrappanel
{
get { return (WrapPanel)this.GetValue(Wrappanel); }
set { this.SetValue(Wrappanel, value); }
}
//DependencyProperty for RelativeSource Binding to Wrappanel in Style (Note: the Binding is set inside the Style, not here programmatically in PropertyMetaData)
public static readonly DependencyProperty Wrappanel = DependencyProperty.Register("ParentWrappanel", typeof(WrapPanel), typeof(StretchGrid), new PropertyMetadata(null));
//Constructor
public StretchGrid() : base()
{
Application.Current.MainWindow.Loaded += Init;
Application.Current.MainWindow.SizeChanged += UpdateMargins;
}
private void Init(object sender, RoutedEventArgs e)
{
m_expander = (Expander)this.Parent; //Change when xaml markup hirarchy changes
if(m_expander != null) //(or make it similar to the Wrappanel with
{ //RelativeSource Binding)
m_expander.Expanded += UpdateMargins; //Update when expander is expanded
m_expander.SizeChanged += UpdateMargins; //Update when the expander changes the Size
//Update all StretchGrids on Initialization
UpdateMargins(null, null);
}
}
//Calculate Grid Margin when an according Event is triggered
private void UpdateMargins(object sender, RoutedEventArgs e)
{
if(ParentWrappanel != null)
{
Point delta = m_expander.TranslatePoint(new Point(0d, 0d), ParentWrappanel);
//Create negative Margin to allow the Grid to be rendered outside of the Boundaries (full column under the Image)
this.Margin = new Thickness(-delta.X, 0, delta.X + m_expander.ActualWidth - ParentWrappanel.ActualWidth, 0);
//Theese Values arent calculated exavtly, just broad for example purpose
}
}
}
This is a little bit tricky to incorporate in existing Code. A working Example may help and is found HERE.
SideNote: If i got more time i would pack this stuff into a bigger Custom Control to use it like a normal UIElement (eg. Border). This would improve the re-usability and usability quite a lot.
EDIT
The 'newest' element in the ListView is also atop of the Z-Index, therefore it is 'above' the expanded StretchGrid and some Controls can not be clicked because they are 'behind' the next ListviewItem.
To fix this, add
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="1" />
</Trigger>
</Style.Triggers>
to the ListViewItem Style. Now when its visible it will place itself atop the other Controls.
This is what I have done so far:-
Images listed:
An Image Clicked - will show the Details view below it:
At the moment I am not closing other Detail Views - so below can happen for me.
However, all this needs is to propagate an event that some other item has been clicked. and close all other Detail Views (its mvvm and driven by a bool property)
======================================================
I have a Base UserControl which will load Items in a Top Down manner:
<Grid>
<Grid>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding GridData, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
Each Row (Item) of this UserControl itself uses another ItemsControl. So that Images are lined up horizontally. Also, Each Item has a Details View Section (Border with Visibility controlled by Image clicks)
<UserControl.Resources>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding ImagesList, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="5" Command="{Binding DataContext.ItemClickedCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding Image, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Name="DisplayText" Grid.Row="1" HorizontalAlignment="Center" Text="{Binding DisplayText, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border Margin="2" Grid.Row="1" Background="Black" Padding="10"
Visibility="{Binding ShowDetailsPanel, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="White" CornerRadius="10">
<Image Margin="5" Source="{Binding SelectedImage.Image, UpdateSourceTrigger=PropertyChanged}"/>
</Border>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.ImageName, StringFormat={}Filename: {0}}" Foreground="White"/>
<TextBlock Grid.Row="1" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.Size, StringFormat={}Size: {0} bytes}" Foreground="White"/>
<TextBlock Grid.Row="2" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.Location, StringFormat={}Path: {0}}" Foreground="White"/>
<TextBlock Grid.Row="3" Margin="20 20 0 0" FontSize="15"
Text="{Binding SelectedImage.CreatedTime, StringFormat={}Last Modified: {0}}" Foreground="White"/>
</Grid>
</Grid>
</Border>
</Grid>
</Grid>

WPF Creating DataTemplate for a control that doesn't have a DataTemplate property

I'm creating a custom TickBar for a Slider. This CustomTickBar allows me to put different markers on the Slider. I'm gonna use the following model:
Interface IModel
{
string Id;
}
Class Model1 : IModel
{
string Id;
string SomeProperty;
}
Class Model2 : IModel
{
string Id;
string SomeOtherProperty;
}
The idea is I provide a List<IModel> to this TickBar control and based on the type of IModel the marker icon would change; e.g. for Model1 it would be a triangle and for Model2 it would be a rectangle. I understand this would be possible using a DataTemplate. But WPF TickBar doesn't have a DataTemplate property. Now is there a way I can do this using a DataTemplate property and subclassing TickBar?
Note: I understand I can create custom tick using OnRender(), but I'm trying to check if there's a way to do it by writing as less code-behind as possible.
TickBar does not have default style, so it looks like using OnRender is the way they designed it.
Another solution I'm think about, would be:
Create custom control, you own TickBar. Maybe inherit from TickBar.
You can set your own style for this custom TickBar, even based on some of your model data.
Use themes/generic.xaml and this code to apply custom style for your control:
static void MyCustomTickBar() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomTickBar), new FrameworkPropertyMetadata(typeof(MyCustomTickBar)));
}
Edit template of Slider and use you new TickBar instead of built in one.
Here is default template for Slider. I used style snooper to extract it. Sorry, I could not provide it in my answer, it's too long.
Try to create a style for the slider first.
e.g. Something like this:
<Window.Resources>
<SolidColorBrush x:Key="HorizontalSliderTrackNormalBackground" Color="#FFE7EAEA"/>
<LinearGradientBrush x:Key="HorizontalSliderTrackNormalBorder" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFAEB1AF" Offset="0.1"/>
<GradientStop Color="#FFAEB1AF" Offset=".9"/>
</LinearGradientBrush>
<Style x:Key="SliderRepeatButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Rectangle Fill="Transparent"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomThumbForSlider" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Ellipse Fill="#009EFF" Stroke="#009EFF" Height="14" Width="14"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MyCustomStyleForSlider" TargetType="{x:Type Slider}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Slider}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TickBar x:Name="TopTick" Visibility="Collapsed" Fill="{TemplateBinding Foreground}" Placement="Top" Height="10" Grid.Row="2"/>
<TickBar x:Name="BottomTick" Visibility="Collapsed" Fill="{TemplateBinding Foreground}" Placement="Bottom" Height="10" Grid.Row="2"/>
<Border x:Name="TrackBackground"
Background="{StaticResource HorizontalSliderTrackNormalBackground}"
BorderBrush="{StaticResource HorizontalSliderTrackNormalBorder}"
BorderThickness="2" CornerRadius="1"
Margin="5,0" VerticalAlignment="Center" Height="10.0" Grid.Row="1" >
<Canvas Margin="-6,-2">
<Rectangle Visibility="Hidden" x:Name="PART_SelectionRange" Height="6.0"
Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"
Stroke="{DynamicResource {x:Static SystemColors.ControlDarkDarkBrushKey}}"
StrokeThickness="2.0"/>
</Canvas>
</Border>
<Track x:Name="PART_Track" Grid.Row="1" >
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource SliderRepeatButtonStyle}" Command="{x:Static Slider.DecreaseLarge}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource SliderRepeatButtonStyle}" Command="{x:Static Slider.IncreaseLarge}"/>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="Thumb" Style="{StaticResource CustomThumbForSlider}" Background="Black"/>
</Track.Thumb>
</Track>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
You can then define a Slider which uses the style:
<Slider Name="CustomSlider" Style="{StaticResource MyCustomStyleForSlider}"/>
In order to change the style depending on some properties you can add a datatrigger. just replace the existing style with a new one:
<Style x:Key="CustomThumbForSlider" TargetType="{x:Type Thumb}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Ellipse Fill="#009EFF" Stroke="#009EFF" Height="14" Width="14"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsDifferent}" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Ellipse Fill="#009055" Stroke="#009055" Height="14" Width="14"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
This new Style is going to change the appearance when the datacontext of slider has a different value for the property "IsDifferent".
<Slider Name="CustomSlider" Style="{StaticResource MyCustomStyleForSlider}" DataContext="{Binding Path=MyContext}"/>
Of course it would be possible to replace the green ellipse with a different shape of your liking and also use a different property.
For the templating problem it is usally best to use a ControlTemplate or alternativly a ContentControl whose DataTemplate can be set freely and which will act as a parent for your own controls.
I've recently tried overriding onrender for a custom slider myself and it's tricky. I wouldn't go that route.
I suggest you consider adding another control to hold the markers and make that match the height or width of your slider.
If your "ticks" at fixed then that could be just a uniformgrid containing paths and you use a resource to define their data using a DynamicResource geometry. You can switch out the geometry by merging different ones or datatemplate it.

wpf customControl binding itemscontrol DataTemplate button

i am new to WPF so i am stuck with some binding on my customcontrol click button.
I have textbox that has watermark and selctedItems properties. Control if selecteditems != null display them in control as this:
link to picture
[http://s29.postimg.org/p25qgqzwn/image.jpg][1]
Now i need to wire up X buttons to delete that item from selectedItems source.
I am trying to do this in OnApplyTemplate method but i dont know how to get to that button in itemscontrol to attach mouse click event.
my Xaml
<Style TargetType="{x:Type local:TextBoxPicker}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="WatermarkTemplate" Value="{StaticResource DefaultWatermarkTemplate}"/>
<Setter Property="SelectedItemsTemplate" Value="{StaticResource DefaultSelectedItemsTemplate}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TextBoxPicker}">
<Grid>
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</Border>
<Border BorderBrush="Red" BorderThickness="1">
<ContentControl x:Name="PART_WatermarkHost"
Content="{TemplateBinding Watermark}"
ContentTemplate="{TemplateBinding WatermarkTemplate}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
IsHitTestVisible="False"
Margin="{TemplateBinding Padding}"
Visibility="Collapsed" />
</Border>
<Border BorderBrush="Green" BorderThickness="1">
<ItemsControl x:Name="PART_SelectedItemsHost"
ItemsSource="{TemplateBinding SelectedItems}"
ItemTemplate="{TemplateBinding SelectedItemsTemplate}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Margin="{TemplateBinding Padding}"
Visibility="Visible">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel>
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
</Grid>
....
</ControlTemplate>
</Setter.Value>
</Setter>
and my ItemTemplate is looking like this
xaml:
<DataTemplate x:Key="DefaultSelectedItemsTemplate" >
<Border x:Name="selectedItemBorder" BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Margin="5,1,1,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="15"/>
</Grid.ColumnDefinitions>
<!--<TextBlock Grid.Column="0" Text="{Binding RelativeSource={RelativeSource Self}, Path=Ime}" Margin="5,0,3,0"></TextBlock>-->
<TextBlock Grid.Column="0" Text="{Binding}" Margin="5,0,3,0"></TextBlock>
<Button x:Name="PART_selectedItemButton" BorderThickness="0" Grid.Column="1" >X</Button>
</Grid>
</Border>
</DataTemplate>
.cs code
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ItemsControl selectedItem = GetTemplateChild("PART_SelectedItemsHost") as ItemsControl;
if (selectedItem != null)
{
selectedItem.MouseLeftButtonDown += new MouseButtonEventHandler(selectedItemBorder_MouseLeftButtonDown);
// blind click on X buttons in ItemsControl
}
}
so how can i bind click on that "X" button on items in code-behind ?
First the viewmodel with the ObservableCollection and the Command that will execute the X:
private ObservableCollection<string> items = new ObservableCollection<string>() { "One", "Two", "Three" };
public ObservableCollection<String> Items
{
get
{
return items;
}
set
{
items = value;
NotifyPropertyChanged();
}
}
public Command<String> DeleteItem
{
get
{
return new Command<string>((item) =>
{
if (items.Contains(item))
{
items.Remove(item);
}
NotifyPropertyChanged("Items");
});
}
}
Now the XAML:
Resources, where it is binded to the 'parent' datacontext, this is a easy way to know where the binding is going:
<Page.Resources>
<DataTemplate x:Key="DefaultSelectedItemsTemplate" >
<Border x:Name="selectedItemBorder" BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Margin="5,1,1,1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="15"/>
</Grid.ColumnDefinitions>
<!--<TextBlock Grid.Column="0" Text="{Binding RelativeSource={RelativeSource Self}, Path=Ime}" Margin="5,0,3,0"></TextBlock>-->
<TextBlock Grid.Column="0" Text="{Binding}" Margin="5,0,3,0"></TextBlock>
<Button x:Name="PART_selectedItemButton" BorderThickness="0" Grid.Column="1" Command="{Binding DataContext.DeleteItem, ElementName=ItemsControlInstance}" CommandParameter="{Binding}">X</Button>
</Grid>
</Border>
</DataTemplate>
</Page.Resources>
And finally the itemscontrol:
<ItemsControl x:Name="ItemsControlInstance" ItemTemplate="{StaticResource DefaultSelectedItemsTemplate}" ItemsSource="{Binding Items}"/>
Finally i manage to make it work, with help and guidance from Jaun it still didnt work (i tried probably 10 different things and it was in DataContext .. it was never binded.
So on my override OnApplyTemplate i added this.DataContext = this ... so i missed that part.
I used AttachedCommandBehavior (nuget) for command
code:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DataContext = this;
ItemsControl itmControl = GetTemplateChild("PART_SelectedItemsHost") as ItemsControl;
if (itmControl != null)
{
itmControl.MouseLeftButtonDown += new MouseButtonEventHandler(itmControl_MouseLeftButtonDown);
// blind click on X buttons in ItemsControl
}
}

Binding a TextBlock back to ListBoxItem ItemSource

I have a ListBoxItem Style that I am trying to modify so that it will show character ellipsis when the list box is made to small. To do that I've had to get rid of the ContentPresenter in our code and replace it with a TextBlock. The ListBoxes that this is applied to are all bound via the ItemSource property.
Here is my code.
<Style x:Key="ListBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="White"/>
<Setter Property="Margin" Value="0,0,0,0"/>
<Setter Property="Padding" Value="0,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<Border x:Name="Bd" SnapsToDevicePixels="true">
<!-- Before this used to be ContentPresenter but I switched it to TextBlock to get it the TextTrimming property. I can't find the right way to bind the data though.-->
<TextBlock Text="{TemplateBinding DisplayMemberPath}" TextTrimming="CharacterEllipsis" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<Rectangle x:Name="HoverRectangle"
Stroke="{StaticResource Gold}"
StrokeDashCap="Square"
StrokeThickness="0"
SnapsToDevicePixels="True" />
<Rectangle x:Name="KeyboardFocusRectangle"
Height="Auto"
SnapsToDevicePixels="True"
Stroke="{StaticResource BrightBlue}"
StrokeDashCap="Square"
StrokeThickness="0" />
</Grid>
<ControlTemplate.Triggers>
<!-- Bunch of Triggers in here -->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My current TextBlock Text binding (Text="{TemplateBinding DisplayMemberPath}") is not working. What should the binding be in order to work correctly?
Your only reasonable choice here is to assume the data context of the ListBoxItem is a string, or can be displayed as such:
<TextBlock Text="{Binding}" .../>

How do you set a non-dependancy property from the viewmodel for a dynamic control

I know my subject line is a bit confusing, and that is either because I have confused myself so totally that I am missing a simple solution or that this really is one of those tough nuts.
My view has a portion that is a set of tabs that are created based on an observable list in the viewmodel. Each tab has a couple of embedded controls, one of which is a RichTextBox. One of the big limitations of a RichTextBox is that the Document is not a dependency property, and I know one solution would be to sub-class the control and just add a dependency property.
I have approached this problem from a couple of directions, and I am just not making headway must likely due to my still growing knowledge of .net. I believe my biggest problem is the separation of the view and VM. My view has the VM as a data source, and my original group of 8 items is correctly generated. However, even though the class used in the observable list has a flowdocument as one of its items there is no XAML way to bind that flowdocument to the RichTextBox. There does not seem to be a newTabAdded type event for TabControl and the Initialized event for the RichTextBox does not seem to even make it to the code behind in the View. Even if I could capture a useful event in the code behind I can not seem to understand how the Relay command works on a control that does not implement the ICommand source to pass the event to my VM.
So my question is, what pattern or class is used that will allow access in the VM to a specific control in the view that is created dynamically based on an observable list and can change dynamically during runtime? I am doing this under VS2010 Professional for .net4.0 and have access to blend as well. My current requirement is to set the document only once, but in a perfect world I would prefer to be able to change the document.
Here is the current XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Luna"
xmlns:CustomControlLibrary="clr-namespace:CustomControlLibrary;assembly=CustomControlLibrary"
x:Class="ClientUI.View"
Title="ViewWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="BSwitchTemplate">
<StackPanel x:Name="Switch_Listing">
<Button Content="{Binding SwitchInstance.boxName, Mode=OneWay}" Width="75" Style="{DynamicResource swmainbuttonTemplate}">
<Button.Resources>
<Style x:Key="ButtonFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="3" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<LinearGradientBrush x:Key="ButtonNormalBackgroundFill" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF" Offset="0"/>
<GradientStop Color="#FFF0F0EA" Offset="0.9"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="ButtonBorder" Color="#FF003C74"/>
<Style x:Key="swmainbuttonTemplate" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
<Setter Property="Background" Value="{StaticResource ButtonNormalBackgroundFill}"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorder}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding IsDefaulted}" SnapsToDevicePixels="true" ThemeColor="NormalColor">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Microsoft_Windows_Themes:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="RenderDefaulted" TargetName="Chrome" Value="true"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="true">
<Setter Property="RenderPressed" TargetName="Chrome" Value="true"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Resources>
</Button>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="Template_swTabHeader">
<Grid Height="Auto" Width="Auto">
<TextBlock TextWrapping="Wrap" Text="{Binding SwitchInstance.boxName, Mode=OneWay}" FontFamily="Times New Roman" FontSize="13.333" HorizontalAlignment="Center" Width="Auto" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Template_swTabContent">
<Grid>
<RichTextBox x:Name="richTextBox" Margin="0,0,8,23.84" FontFamily="Lucida Console" Initialized="AssignDocument">
</RichTextBox>
<CustomControlLibrary:CLI_EntryBox HorizontalAlignment="Stretch" Height="20.5" Margin="2,0,8,0" TextWrapping="Wrap" Text="CLI_EntryBox" VerticalAlignment="Bottom"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource VMDataSource}}">
<Grid.RowDefinitions>
<RowDefinition Height="0.552*"/>
<RowDefinition Height="0.448*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.454*"/>
<ColumnDefinition Width="0.546*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="mainswitchlist" HorizontalAlignment="Left" Margin="8,8,0,34.5" Width="105" SelectionMode="Multiple" ItemTemplate="{DynamicResource BSwitchTemplate}" ItemsSource="{Binding fabric_switches}" FontFamily="Times New Roman" Grid.RowSpan="2" />
<TabControl x:Name="swTabs" Margin="117,8,51,34.5" Grid.ColumnSpan="2" ItemsSource="{Binding fabric_switches}" ItemTemplate="{DynamicResource Template_swTabHeader}" ContentTemplate="{DynamicResource Template_swTabContent}" Grid.RowSpan="2" DataContext="{StaticResource VMDataSource}" SelectionChanged="swTabs_SelectionChanged" />
<Button Content="Button" Grid.Column="1" Height="26" HorizontalAlignment="Left" Margin="242,18,0,0" Name="button1" VerticalAlignment="Top" Width="27" />
</Grid>
Couple of options:
An attached behavior
Use an EventSetter in the ItemContainerStyle for the TabControl to perform some logic when each TabItem loads.

Resources