I'm having a strange issue with my ContextMenu. The ItemsSource is a List<Layer> Layers;, where the Layer class overrides the ToString() to return the Name property.
If I don't use any ItemContainerStyle for the context menu, it all works fine - it takes the Layer object and displays the ToString() of that object, like it should. When I add the ItemContainerStyle, it shows an empty string.
Here's the XAML:
<Style x:Key="myItemControlTemplate" TargetType="{x:Type MenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border SnapsToDevicePixels="True" Height="32" Width="200" Background="White">
<Grid VerticalAlignment="Center" Height="Auto" Width="Auto" Background="{x:Null}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{TemplateBinding Header}" VerticalAlignment="Top" Foreground="#FF3B3D52"
Grid.Column="1" Margin="6,0,0,0"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Button ContextMenuService.IsEnabled="False">
<Button.ContextMenu>
<ContextMenu FontFamily="Global Sans Serif" Height="Auto" Width="200" Padding="0,6" VerticalOffset="5" BorderThickness="0"
HasDropShadow="True" ItemContainerStyle="{StaticResource myItemControlTemplate}">
</ContextMenu>
</Button.ContextMenu>
</Button>
And here's how I fire it:
private void btn_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
ContextMenu ctm = btn.ContextMenu;
ctm.ItemsSource = Layers;
ctm.PlacementTarget = btn;
ctm.Placement = PlacementMode.Bottom;
ctm.IsOpen = true;
}
Could it be that for some reason this binding gets busted somehow?
Text="{TemplateBinding Header}"
BTW, if I change the layers list to be a List<string> and just feed it the names of the layers, it works correctly with the ItemContainerStyle.
What am I missing?
The issue is the TemplateBinding. This is a less powerful but optimized variant of a relative source binding to a templated parent, but it comes at the expense of several limitations, like not supporting two-way binding. Although not officially stated in the documentation, it seems that this binding does not support conversion of the underlying type to string, as ToString is never called in this case.
Replace the TemplateBinding with a binding to the templated parent using relative source.
<TextBlock x:Name="textBlock"
HorizontalAlignment="Left" TextWrapping="Wrap"
Text="{Binding Header, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Top" Foreground="#FF3B3D52"
Grid.Column="1" Margin="6,0,0,0"/>
Related
I can't get any further.
My structure looks like this:
ViewModel (Class)
Property Number (Class, iValue)
Value, InputValidationInfo, etc.
Property Icon (Class, iValue)
Value, InputValidationInfo, etc.
The DataContext of the page is the ViewModel. There are input fields on the page. These are assigned to one of the iValues (Number, Icon). All input fields have a ContentTemplate. The ContentTemplate contains some elements which has bindings to the properties of the iValue-class.
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" Background="{TemplateBinding Background}">
<DockPanel>
<Border Width="10" Background="{Binding InputValidation, Converter={StaticResource InputValidationToBrushConverter}}" Margin="0">
<Border.ToolTip>
<ToolTip Visibility="{Binding InputValidationInfoCount, Converter={StaticResource CountToVisibleConverter}}" >
<ItemsControl ItemsSource="{Binding InputValidationInfo}"/>
</ToolTip>
</Border.ToolTip>
</Border>
<Border x:Name="ContentBorder" Margin="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter Margin="5 1 0 1" x:Name="myButtonContent" HorizontalAlignment="Left" VerticalAlignment="Center" TextElement.Foreground="{StaticResource ButtonForegroundBrush}"/>
<StackPanel x:Name="Buttons" Grid.Column="1" Orientation="Horizontal" Visibility="Collapsed">
<Button x:Name="ResetButton" Style="{StaticResource styleInputBoxSubButton}" Content="{StaticResource symbolCancel}" FontFamily="{StaticResource symbolCancelFF}" Command="{Binding ResetValueCommand}"/>
</StackPanel>
</Grid>
</Border>
</DockPanel>
</Border>
</ControlTemplate>
And this is how it looks like:
<Button x:Name="btnIcon" Grid.Column="1" Grid.Row="2" DataContext="{Binding Icon}"
Style="{StaticResource styleInputButton}" MinHeight="37"
FontFamily="{Binding Value, Converter={StaticResource NumberToUserIconFontFamilyConverter}}"
Content="{Binding Value, Converter={StaticResource NumberToUserIconConverter}}"
Click="IconButton_Click"/>
My problem are the click events. The small [X] button is bound to the iValue classes with an ICommand. This works well. I can set the iCommand as binding in the ControlTemplate, because this is always the same iValue-property (ResetValueCommand).
But, a click of the "rest" of the actual button should call a method in the ViewModel. And the name of this method is not always the same. It cannot set in the ContentTemplate.
I cannot bind the command to the ViewModel, because the DataContext is already the iValue.
I have tried to use the Click event. But this event is also triggered by clicking the [X]-button.
This button should be used often. An elegant solution is needed. The most simple thing would be if I could specify the data context and binding for a particular element in the ContentTemplate in page-XAML. Or specify the click event of an element in the ContentTemplate in page-XAML.
I cannot bind the command to the ViewModel, because the DataContext is already the iValue ...
You can specify a source for the binding regardless of its DataContext:
<Button x:Name="btnIcon" ...
Command="{Binding DataContext.CommandPropertyOfViewModel,
RelativeSource={RelativeSource AncestorType=Page}}"/>
I got an ItemsControl which uses a Canvas as ItemsPanel and its items are rendered to different WPF shapes depending on the bound type, basically like this:
<ItemsControl ItemsSource="{Binding PreviewShapes}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:UiPreviewLineViewModel}">
<Line X1="{Binding Start.X}" Y1="{Binding Start.Y}"
X2="{Binding End.X}" Y2="{Binding End.Y}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Line" ToolTip="{Binding Text}">
</Line>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
<Ellipse Canvas.Left="{Binding UpperLeft.X" Canvas.Top="{Binding UpperLeft.Y}"
Width="{Binding Width}" Height="{Binding Height}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
</Ellipse>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="SketchCanvas" ClipToBounds="False">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
So I basically add objects to PreviewShapes of the viewmodel and depending on the type they are rendered to WPF Lines or Ellipses. That basically works but the attached properties Canvas.Left and Canvas.Top are ignored, even when using static values.
Also VS or ReSharper notifies me that the attached property has no effect in the current context.
How can I position the Ellipse on the canvas without using the attached properties? Or what other solution would be appropiate?
Unfortunately nobody felt like posting an answer.
First, Clemens links are helpful. The items will be inside a ContentPresenter which is the reason why setting Canvas.Left/Top on the Ellipsis does not work.
Solution 1
By adding a style to the item container the bindings for the position can be set there:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding UpperLeft.X}" />
<Setter Property="Canvas.Top" Value="{Binding UpperLeft.Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
This works but the DataTemplate placing <Line> will produce binding warnings because that view model does not have a property UpperLeft. Nevertheless it works for the ellipsis and the lines are placed by their X1, Y1, X2 and Y2 values.
Solution 2
If you would like to use a more fine grained control approach you can set the attached Canvas properties to the ContentPresenter by proxing them with a custom behaviour / attached property. Let's name it CanvasPointProxyBehavior, you could use it for the Ellipse like this:
<DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
<Ellipse behaviors:CanvasPointProxyBehavior.Point="{Binding UpperLeft}"
Width="{Binding Width}" Height="{Binding Height}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
</Ellipse>
</DataTemplate>
The Line will not need it. The code for this attached property might look like this:
public class CanvasPointProxyBehavior
{
public static readonly DependencyProperty PointProperty = DependencyProperty.RegisterAttached("Point", typeof(Point), typeof(CanvasPointProxyBehavior), new UIPropertyMetadata(null, PointChangedCallback));
public static void SetPoint(DependencyObject depObj, Point point)
{
depObj.SetValue(PointProperty, point);
}
public static Point GetPoint(DependencyObject depObj)
{
return depObj.GetValue(PointProperty) as Point;
}
private static void PointChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = (dependencyObject as UIElement);
if (uiElement == null) return;
UIElement elementToBePositioned = uiElement;
var visualParent = VisualTreeHelper.GetParent(uiElement);
if (visualParent is ContentPresenter)
{
elementToBePositioned = visualParent as ContentPresenter;
}
var point = e.NewValue as Point;
if (point != null)
{
Canvas.SetLeft(elementToBePositioned, point.X);
Canvas.SetTop(elementToBePositioned, point.Y);
}
}
}
Hoping someone will find one or both solution useful.
Please note that I encountered the same ReSharper warning message as #ZoolWay, but in my case it was within a data grid, where I wanted the button on the right to be right-aligned instead of left-aligned:
Attached property setting "Grid.Column" has no effect in current
context and can be removed.
Here is the code where I had the warning, on the Button Grid.Column="2":
<Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
<!-- more labels, etc... -->
<Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
<StackPanel Orientation="Horizontal" Background="Transparent">
<Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Border>
This is how I fixed the warning, where I moved the button below the first StackPanel:
<Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
<!-- more labels, etc... -->
</StackPanel>
<Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
<StackPanel Orientation="Horizontal" Background="Transparent">
<Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
</StackPanel>
</Button>
</Grid>
</Border>
HTH
I have a button subclass called MenuButton.
public class MenuButton : Button
{
public string Caption
{
get { return (string)GetValue(CaptionProperty); }
set { SetValue(CaptionProperty, value); }
}
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(MenuButton), new UIPropertyMetadata(null));
public UserControl Icon
{
get { return (UserControl)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(UserControl), typeof(MenuButton),
new PropertyMetadata(null));
}
In the style I want to show an icon that was created using paths from SVG files. I have created a User Control containing the XAML for the icon:
<UserControl x:Class="WpfApplication1.Views.ScopeIcon"
.
.
.
>
<Viewbox Height="55"
Width="55">
<Grid>
<Path Fill="LightBlue" Data="M98.219,48.111C97..."/>
<Path Fill="LightBlue" Data="M98.219,46.948C97...."/>
</Grid>
</Viewbox>
</UserControl>
And here's the style:
<Style TargetType="Button"
x:Key="TestButtonStyle">
<Setter Property="Height" Value="140"/>
<Setter Property="Width" Value="195"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MenuButton}">
<Border x:Name="TheBorder">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Row="0"/> <===== THE USER CONTROL WILL GO HERE
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="{TemplateBinding Caption}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="5"
Foreground="White"
FontSize="14"
TextAlignment="Center"
TextWrapping="WrapWithOverflow"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I will set it here:
<ListBox Grid.Row="0"
ItemsSource="{Binding MainTools}" >
<ListBox.ItemTemplate>
<DataTemplate>
<controls:MenuButton Caption="{Binding Caption}"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}"
Icon=""/> <============= HOW DO I PUT THE SCOPEICON USER CONTROL HERE?
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm essentially trying to nest user controls, but I want to tell the button, in XAML, what UserControl to use for its icon.
Thank you
First, you need to update your style to set Content property on your ContentPresenter the same way you did for Text property on TextBlock.
<ContentPresenter Grid.Row="0" Content="{TemplateBinding Icon}"/>
And, then to set Icon on the MenuButton in your DataTemplate update your MenuButton element in DataTemplate to something like:
<controls:MenuButton Caption="Caption"
Margin="2"
Width="100"
Style="{StaticResource TestButtonStyle}"
VerticalAlignment="Top"
Command="{Binding Path=ButtonClick}"
CommandParameter="{x:Static enums:Tabs.Oscilloscope}">
<controls:MenuButton.Icon>
<views:ScopeIcon />
</controls:MenuButton.Icon>
</controls:MenuButton>
Basically, I'd like to make the WPF DataGrid control layout its columns exactly the way the WinForms DataGridView does
And more specifically, here is the behaviour I'm looking for:
The grid control should take up the space it's given (i.e. however much space is available in its parent control for it to use). Here I am referring just to the control, and not to the columns.
The columns created (whether automatically or manually) may or may not take up all this space.
If there is extra space left over after the columns are created, the last column should not be expanded to fill this space
If there is extra space left over after the columns are created, an empty column with nothing in it should not be created to fill this extra space
From what I can tell, in WPF the last two bullet points seem to be mutually exclusive and you must choose one or the other. Has anyone found a way to do both? I've searched quite a bit and haven't found quite what I'm looking for, however all the posts I'm finding tend to be a couple years old so I'm hoping someone has figured this thing out by now.
EDIT: sa_ddam213, here's a quick xaml project I put together to test your suggestion.
<Window x:Class="DataGridFix.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataGridFix"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="data"
ObjectType="{x:Type local:TestObject}"
MethodName="GetTestData" />
</Window.Resources>
<StackPanel>
<DataGrid HorizontalAlignment="Left" ColumnWidth="Auto" Height="150" VerticalAlignment="Top" ItemsSource="{Binding Source={StaticResource data}}" />
</StackPanel>
</Window>
And here's the code behind:
namespace DataGridFix
{
public class TestObject
{
public int Id { get; set; }
public string Name { get; set; }
public static List<TestObject> GetTestData()
{
var items = new List<TestObject>();
items.Add(new TestObject() { Id = 1, Name = "Joe" });
items.Add(new TestObject() { Id = 2, Name = "Matt" });
items.Add(new TestObject() { Id = 3, Name = "Hal" });
return items;
}
}
}
Really the only noteable thing I see from your suggestion is to set HorizontalAlignment to Left. I did that, and tried setting the ColumnWidth to the various settings but had the same problem with each (except * of course... technically I can mess that one up to but I won't go into that).
If you use your mouse to expand any of the columns, and then decrease the column size then the empty filler column appears. The only other difference I noted from your post was that you put your DataGrids in a StackPanel since you had more than one of them. I tried that just for the heck of it but same result. If you see any other difference between what I'm doing and what you suggested please let me know.
In order to get the behavior you want, you probably have to modify the control template of the DataGrid.
Take a look at the code. I have gotten pretty close to the WinForms DataGridView look i think.
To remove the extra column you have to remove the filler column from the DataGridColumnHeadersPresenter. I have just commented it out. The rest is just the default template.
The other modification is to the template of the DataGrid.
By setting HorizontalAlignment="Left" on the ScrollContentPresenter, the rows no longer take up all the width of the control.
Those are the only 2 changes I have made to the default templates.
<Style TargetType="{x:Type DataGridColumnHeadersPresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
<Grid>
<!-- Remove this filler column -->
<!--<DataGridColumnHeader x:Name="PART_FillerColumnHeader" IsHitTestVisible="False" />-->
<ItemsPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGrid}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True"
Padding="{TemplateBinding Padding}">
<ScrollViewer Focusable="false" Name="DG_ScrollViewer">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Command="{x:Static DataGrid.SelectAllCommand}"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=CellsPanelHorizontalOffset}"
Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type DataGrid}, ResourceId=DataGridSelectAllButtonStyle}}"
Focusable="false"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.All}}" />
<DataGridColumnHeadersPresenter Grid.Column="1" Name="PART_ColumnHeadersPresenter"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Column}}"/>
<!-- Set HorizontalAlignment="Left" to have the rows only take up the width they need and not fill the entire width of the DataGrid -->
<ScrollContentPresenter HorizontalAlignment="Left" x:Name="PART_ScrollContentPresenter" Grid.Row="1" Grid.ColumnSpan="2" CanContentScroll="{TemplateBinding CanContentScroll}" />
<ScrollBar Grid.Row="1" Grid.Column="2" Name="PART_VerticalScrollBar"
Orientation="Vertical"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{Binding Path=VerticalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=NonFrozenColumnsViewportHorizontalOffset}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollBar Grid.Column="1"
Name="PART_HorizontalScrollBar"
Orientation="Horizontal"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Value="{Binding Path=HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
</Grid>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UPDATE
It does indeed look like there is a difference between .NET 4 and .NET 4.5.
I develop on a Windows 8 machine with Visual Studio 2012, so as a test I tried targeting .NET 4 to see if I could replicate the wrong behavior. But it still worked fine.
To be sure, I tried running the app on a different machine with only .NET 4 installed, and here the empty rows showed up when making a column bigger and then smaller again.
The issue seems to be that the DataGridRows are not behaving properly. When running on a machine with only .NET 4 installed, they keep their current size when making the column smaller. On .NET 4.5 they resize as expected.
The new solution to get the behavior you need, is actually much simpler than the previous one.
By simply setting the HorizontalAlignment on the DataGridRows to left, and removing the filler column, it works on both .NET 4 and .NET 4.5. And there is no longer a need to replace the entire template of the DataGrid.
<Style TargetType="DataGridRow">
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style TargetType="DataGridColumnHeadersPresenter">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
<Grid>
<ItemsPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There ar plenty of layout options for Columns in WPF, its just a matter of choosing what you want to be displayed.
Pixel
SizeToCells
SizeToHeader
Auto
Proportional(*)
And if you set the HorizontalAlignment to Left the DataGrid will resize to fit its contents based on the ColumnWidth you picked.
Here is a example of some of the avaliable column settings
<StackPanel>
<DataGrid HorizontalAlignment="Left" ColumnWidth="100" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
<DataGrid HorizontalAlignment="Left" ColumnWidth="SizeToCells" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
<DataGrid HorizontalAlignment="Left" ColumnWidth="SizeToHeader" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
<DataGrid HorizontalAlignment="Left" ColumnWidth="Auto" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
<DataGrid HorizontalAlignment="Left" ColumnWidth="*" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
</StackPanel>
I am having some difficulty figuring out how to template the following TreeView item layout:
I have several items, SearchList, which contains a collection of Search, which contains a collection of DataSet (sort of, but that is beside the point). What I am having difficulty with is styling each node level the way I want. I am using MVVM, and the TreeViews ItemsSource property is set to an ObservableCollection of SearchListViewModels which in turn contain my objects all the way down the object tree.
I can successfully style the SearchList HierarchicalDataTemplate to display them correctly. Where I get hung up is on SearchTerm nodes styling. I want the DataSets to be represented in a wrap panel or uniform grid (I haven't decided yet) to the right of the SearchTerm content area. I have modified a TreeViewItem control template to behave this way I think), however if I set it in the ItemContainerStyle property of the Search HierarchicalDataTemplate, it does nothing. All that gets displayed is the content for the Search.
My Altered TreeViewItem Template
<Style TargetType="{x:Type TreeViewItem}" x:Key="AlteredTreeViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="19" />
<ColumnDefinition Width="0.414*" />
<ColumnDefinition Width="0.586*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Bd" HorizontalAlignment="Stretch"
Grid.Column="1" Grid.ColumnSpan="1" Background="#7F058956">
<ContentPresenter x:Name="PART_Header" Margin="10,0" />
</Border>
<WrapPanel x:Name="ItemsHost"
Grid.Column="2" IsItemsHost="True"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My Search Hierarchical Data Template
<HierarchicalDataTemplate DataType="{x:Type local:SearchViewModel}" ItemsSource="{Binding MySearch.Custodians}" ItemContainerStyle="{StaticResource AlteredTreeViewItem}">
<TextBlock Text="{Binding MySearch.SearchName}" Foreground="Black" FontFamily="Arial" FontSize="16"/>
</HierarchicalDataTemplate>
Surely it is possible to both style differently and have child items laid out differently? How can this be achieved?
It seems that you are pretty close to what you're after. I tried to recreate your scenario based on the code you posted and I noted some problems with it (which of course are based on my interpretation of the code you posted)
You are missing the ContentSource="Header" part of the ContentPresenter
I think you are applying the ItemContainerStyle at the wrong HierarchicalDataTemplate level. It should be specified on the parent in order to affect the children (in your case SearchListViewModel).
The default Template for TreeViewItem lays out the ContentPresenter in an Auto sized ColumnDefinition so the WrapPanel won't succesfully wrap unless you modify the ItemContainerStyle for the parent as well. I changed it to a UniformGrid in my sample below
With the changes from above and a few other things I got a result that looks like this which hopefully is pretty close to what you're after
I uploaded the sample solution here: https://www.dropbox.com/s/4v2t8imikkagueb/TreeViewAltered.zip?dl=0
And here is the Xaml code for it (too much code to post it all..)
<Window.Resources>
<!-- DataSet-->
<HierarchicalDataTemplate DataType="{x:Type data:DataSet}">
<Border BorderThickness="3"
BorderBrush="Gray"
Background="Green">
<TextBlock Text="{Binding Path=Tables[0].TableName}"
Margin="5"/>
</Border>
</HierarchicalDataTemplate>
<!-- SearchViewModel -->
<HierarchicalDataTemplate DataType="{x:Type viewModel:SearchViewModel}"
ItemsSource="{Binding DataSets}">
<TextBlock Text="{Binding DisplayName}"
Foreground="Black"
FontFamily="Arial"
FontSize="16"/>
</HierarchicalDataTemplate>
<!-- SearchListViewModel -->
<HierarchicalDataTemplate DataType="{x:Type viewModel:SearchListViewModel}"
ItemsSource="{Binding SearchList}">
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="19" />
<ColumnDefinition Width="0.414*" />
<ColumnDefinition Width="0.586*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Bd"
HorizontalAlignment="Stretch"
Grid.Column="1"
Grid.ColumnSpan="1"
Background="#7F058956">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
<UniformGrid x:Name="ItemsHost"
Grid.Column="2"
Columns="3"
IsItemsHost="True"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<TextBlock Text="{Binding DisplayName}"
FontSize="20"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding SearchListViewModels}" />
</Grid>
Something I learnt a long time ago when trying to create a similar interface was that you are better using a ListBox than a TreeView.
Why?
If you only have one level of expansion (as it appears from your sample) you will a lot more control of the layout as you have a single DataTemplate to style.
It is lot easier to customize a ListBox than a TreeView as you do not have be concerned with the GridViewColumnHeader and GridViewColumnPresenters etc.
To get the expansion part (which is why you initially selected a TreeView), simply use a Grid with two rows defined and an Expander in the second row bound to the IsChecked property of a ToggleButton. See the example that I pulled from my Log Viewer.
<DataTemplate>
<Grid Margin="0,0,0,3" Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" SharedSizeGroup="SSG_TimeIcon"/>
<ColumnDefinition Width="120" SharedSizeGroup="SSG_Time"/>
<ColumnDefinition Width="30" SharedSizeGroup="SSG_LevelIcon"/>
<ColumnDefinition Width="70" SharedSizeGroup="SSG_Level"/>
<ColumnDefinition Width="*" SharedSizeGroup="SSG_Message"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ProgramTime -->
<Rectangle Grid.Column="0" Grid.Row="0" Margin="0,0,0,0" Width="16" Height="16" VerticalAlignme="Top" HorizoalAlignme="Stretch" Fill="{StaticResource Icon_Timer}"/>
<TextBlock Grid.Column="1" Grid.Row="0" Margin="5,0,0,0" VerticalAlignme="Top" HorizoalAlignme="Stretch" Text="{Binding Path=TimeStamp, Converter={StaticResource ObjectToStringConverter}}" ToolTip="{Binding Path=ProgramTime}"/>
<!-- Level -->
<Rectangle Grid.Column="2" Grid.Row="0" Margin="10,0,0,0" Width="16" Height="16" VerticalAlignme="Top" HorizoalAlignme="Stretch" Fill="{Binding Path=Level, Converter={StaticResource MappingConverterNinjaLogLevelEnumToBrushResource}}"/>
<TextBlock Grid.Column="3" Grid.Row="0" Margin="5,0,0,0" Text="{Binding Path=LevelFriendlyName}" VerticalAlignme="Top" HorizoalAlignme="Stretch"/>
<!-- Message -->
<StackPanel Grid.Column="4" Grid.Row="0" Margin="10,0,0,0" Orieation="Horizoal" >
<TextBlock Margin="0,0,0,0" Text="{Binding Path=LogMessage}" TextWrapping="Wrap" VerticalAlignme="Top" HorizoalAlignme="Stretch"/>
<ToggleButton x:Name="ExpandExceptiooggleButton" VerticalAlignme="Top" Margin="5,0,0,0" IsChecked="False"
Coe="Show Details" Tag="Hide Details" Style="{StaticResource TextButtonStyle}"
Foreground="{StaticResource BlueBrush}" Background="{StaticResource RedBrush}"
Visibility="{Binding Path=HasException, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<Expander IsExpanded="{Binding Path=IsChecked, ElemeName=ExpandExceptiooggleButton}" Style="{StaticResource CoeExpanderStyle}"
Margin="10,0,0,0" Grid.Column="4" Grid.Row="1">
<Border BorderBrush="{StaticResource DarkGreyBrush}" BorderThickness="1,0,0,0">
<TextBlock Text="{Binding Path=Exception}" Margin="5,0,0,0"/>
</Border>
</Expander>
</Grid>
</DataTemplate>
Can you see how much easier it is to define a header and expandable body. If you do have a need for nested data, add a Level property your view model (you are using MVVM aren't you?!) and then create a IValueConverter that returns a Margin (i.e. Thickness) to fake the indent.