Sharing control instance within a view in WPF - wpf

I'm having some issues with the wpf tab control. Not sure the title of the question is right I will refine it accoring to answers.
I want to create a simple panel system. I want to inject ot my "panel viewModel" 2 view model
MainViewModel will be display as the main area
PanelViewModel will be display as a panel on the right hand side of the view
the panelViewModel will be hidden by default and a button will display it on top of the main view model when needed
The view look like this:
<UserControl.Resources>
<DataTemplate x:Key="MainWindowTemplate" DataType="{x:Type UserControl}">
<ContentPresenter Content="{Binding DataContext.MainViewModel, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Grid.Column="0" ContentTemplate="{StaticResource MainWindowTemplate}" />
<Button Grid.Column="1" Content="{Binding PanelTitle}" Command="{Binding Path=ShowPanelCommand}">
<Button.LayoutTransform>
<RotateTransform Angle="90"/>
</Button.LayoutTransform>
</Button>
</Grid>
<Grid Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl ContentTemplate="{StaticResource MainWindowTemplate}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="2" VerticalAlignment="Stretch" Background="Red" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border HorizontalAlignment="Stretch">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding PanelTitle}" Margin="5,5,0,2" HorizontalAlignment="Left"></TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0,0,5,0" HorizontalAlignment="Right" >
<Button Content="Minimze" Command="{Binding HidePanelCommand}"/>
</StackPanel>
</Grid>
</Border>
<ContentPresenter Grid.Row="1" Margin="2" Content="{Binding PanelViewModel}" VerticalAlignment="Top"/>
</Grid>
</Border>
</Grid>
</Grid>
The view model look like that:
public class TestTabViewModel : ObservableObject
{
#region private attributes
#endregion
public TestTabViewModel(string panelName, object panelViewModel, object mainViewModel)
{
IsPanelHidden = true;
PanelTitle = panelName;
PanelViewModel = panelViewModel;
MainViewModel = mainViewModel;
ShowPanelCommand = new DelegateCommand(() =>ManagePanelVisibility(true));
HidePanelCommand = new DelegateCommand(() => ManagePanelVisibility(false));
}
#region properties
public string PanelTitle { get; private set; }
public bool IsPanelHidden { get; private set; }
public object PanelViewModel { get; private set; }
public object MainViewModel { get; private set; }
public DelegateCommand ShowPanelCommand { get; private set; }
public DelegateCommand HidePanelCommand { get; private set; }
#endregion
#region private methods
private void ManagePanelVisibility(bool visible)
{
IsPanelHidden = !visible;
RaisePropertyChanged(() => IsPanelHidden);
}
#endregion
}
So for so good, this system work fine I aslo added some pin command but I remove them from here to make it "simple".
My problem come when the main view model hold a tab control. In this, case if I select a tab and "open" the panel, the tab selected is "changed". In fact it's not changed it's just that I display another contentControl which is not synchronize with the previouse one. I guess that the view instance is not the same even if the viewmodel behind is.
So how do I share a view instance within a view (or have the selection process synchornized)? My first guest was to use the datatemplate (as show in the example) but it did not solve my problem.
By the way, I know some third-party handling panel docking pin ... (eg avalon) but all the one I found are really too much for my simple need.
Thanks for the help

Your best bet would probably be to replace your two Grids with a single ContentControl, and switch the Template on button click or in a Trigger. This way your actual Content (the TabControl) will be the same, but the template used to display the Content will change
Here's a quick example:
<Window.Resources>
<ControlTemplate x:Key="Grid1Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Left" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
<ControlTemplate x:Key="Grid2Template" TargetType="{x:Type ContentControl}">
<DockPanel>
<Grid Background="CornflowerBlue" Width="100" DockPanel.Dock="Right" />
<ContentPresenter Content="{TemplateBinding Content}" />
</DockPanel>
</ControlTemplate>
</Window.Resources>
<DockPanel>
<ToggleButton x:Name="btnToggle" Content="Toggle View" DockPanel.Dock="Top" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Template" Value="{StaticResource Grid1Template}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=btnToggle, Path=IsChecked}" Value="True">
<Setter Property="Template" Value="{StaticResource Grid2Template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
<TabControl>
<TabItem Header="Tab1" />
<TabItem Header="Tab2" />
<TabItem Header="Tab3" />
</TabControl>
</ContentControl>
</DockPanel>

i dont know if i get what you want but i think that you could do the following.
i assume that you want some "MainViewmodeldata" be presented as your tabcontrol.
so i woulf first create a datatemplate for this.
<UserControl.Resources>
<DataTemplate DataType="{x:Type MainViewmodeldata}">
<TabControl>
<TabItem Header="Tab1">
<TextBlock Grid.Column="1" Text="Tab1Content"/>
</TabItem>
<TabItem Header="Tab2">
<TextBlock Grid.Column="1" Text="Tab2Content"/>
</TabItem>
</TabControl>
</DataTemplate>
</UserControl.Resources>
now i would just bind my this mainviewmodeldata to the contentcontrol and let wpf render it for you. i really dont know if you still need these two grids, cause i dont know what you wanna achieve.
<Grid x:Name="Grid1" Visibility="{Binding IsPanelHidden, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel Grid.Column="1">
<Button x:Name="Button1" Content="Switch look" Command="{Binding ShowPanelCommand}"/>
<TextBlock Text="Look1"/>
</StackPanel>
</Grid>
<Grid x:Name="Grid2" Visibility="{Binding IsPanelHidden, Converter={StaticResource revertBool2VisibilityConverter}}">
<ContentControl Content="{Binding MainViewmodelData}" />
<StackPanel HorizontalAlignment="Right">
<Button x:Name="Button2" Content="Switch look" Command="{Binding HidePanelCommand}"/>
<TextBlock Text="Look2"/>
</StackPanel>
</Grid>
</Grid>

Related

Listbox DataTemplate to be applied to different models

I have two tables that contain just one value that is the key, too.
I created a list box to show it and modify it.
Table 1 is TTypes1 and the field is Type1 String
Table 2 is TTypes2 and the field is Type2 String
I've written this DataTemplate:
<DataTemplate x:Key="ListboxItems">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding}" />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<!-- edit to swap with save -->
<Button
Content=""
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="Save"
Visibility="{Binding IsVisible}" />
<!-- Cancel - visible only on edit -->
<Button
Click="LockUnlock_Click"
Content="{Binding Icon}"
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="{Binding ToolTip}" />
<!-- Delete -->
<Button
Click="LockUnlock_Click"
Content="{Binding Icon}"
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="{Binding ToolTip}" />
<!-- Add -->
<Button
Click="LockUnlock_Click"
Content="{Binding Icon}"
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="{Binding ToolTip}" />
</StackPanel>
</Grid>
</DataTemplate>
Here are the list boxes but I'm not able to get it working as I want.
If I leave it like this:
<Label Grid.Column="0" Content="{Binding}" />
I do not see the text but the type TTypes1 or TTypes2.
But if I write:
<Label Grid.Column="0" Content="{Binding Type1}" />
Then I cannot use it on TType2 list box.
Here is where I use it:
<ScrollViewer
Margin="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ListBox
Margin="2"
AlternationCount="2"
BorderThickness="1"
ItemsSource="{Binding TTypes1}"
SelectedIndex="0"
SelectionMode="Single"
ItemTemplate="{StaticResource ListboxItems}"
Style="{StaticResource MahApps.Styles.ListBox.Virtualized}">
</ListBox>
</ScrollViewer>
and the second one is:
<ScrollViewer
Margin="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ListBox
Margin="2"
AlternationCount="2"
BorderThickness="1"
ItemsSource="{Binding TTypes2}"
SelectedIndex="0"
SelectionMode="Single"
ItemTemplate="{StaticResource ListboxItems}"
Style="{StaticResource MahApps.Styles.ListBox.Virtualized}">
</ListBox>
</ScrollViewer>
What am I missing?
Multiple Data Templates
The usual way to handle this is to create one distinct data template per type, e.g. for TType1 and TType2.
<DataTemplate x:Key="ListboxItemsTType1"
DataType="{x:Type local:TType1}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="{Binding Type1}" />
<!-- ...other markup. -->
</Grid>
</DataTemplate>
<DataTemplate x:Key="ListboxItemsTType2"
DataType="{x:Type local:TType2}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="{Binding Type2}" />
<!-- ...other markup. -->
</Grid>
</DataTemplate>
Reference the specific templates in your ListBoxes. You can also remove the x:Key from the data templates, so they are automatically applied to a matching type in the ListBox. This also works with mixed items in a list.
<ScrollViewer Grid.Row="0"
Margin="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ListBox
ItemTemplate="{StaticResource ListboxItems}"
...
</ListBox>
</ScrollViewer>
<ScrollViewer Grid.Row="1"
Margin="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ListBox
...
ItemTemplate="{StaticResource ListboxItems}"
</ListBox>
</ScrollViewer>
Other Methods
If you really want to keep a single data template, you will have to switch the binding depending on the item type of the object bound as data context. There are multiple ways to achieve this.
Here is an example that uses a converter that converts an object to its type from a related question, copy it. A style for Label will use data triggers to apply the correct binding based on that type.
<local:DataTypeConverter x:Key="DataTypeConverter" />
<DataTemplate x:Key="ListboxItems">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0">
<Label.Style>
<Style TargetType="{x:Type Label}"
BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="Content"
Value="{x:Null}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource DataTypeConverter}}"
Value="{x:Type local:TType1}">
<Setter Property="Content"
Value="{Binding Type1}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource DataTypeConverter}}"
Value="{x:Type local:TType2}">
<Setter Property="Content"
Value="{Binding Type2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<StackPanel Grid.Column="1"
Orientation="Horizontal">
<!-- edit to swap with save -->
<Button Content=""
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="Save"
Visibility="{Binding IsVisible}" />
<!-- Cancel - visible only on edit -->
<Button Click="LockUnlock_Click"
Content="{Binding Icon}"
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="{Binding ToolTip}" />
<!-- Delete -->
<Button Click="LockUnlock_Click"
Content="{Binding Icon}"
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="{Binding ToolTip}" />
<!-- Add -->
<Button Click="LockUnlock_Click"
Content="{Binding Icon}"
ContentTemplate="{StaticResource IconPanelButton}"
DockPanel.Dock="Right"
Style="{StaticResource MahApps.Styles.Button.Flat}"
ToolTipService.ToolTip="{Binding ToolTip}" />
</StackPanel>
</Grid>
</DataTemplate>
Other options that completely rely on code, but are easier to reuse are:
Create a special value converter that does the same as the triggers, return a binding created in code with a property path that is based on type
Create a custom markup extension that automatically chooses the property path based on type
I do not provide examples on these options as they are complex and heavily depend on your requirements. Furthermore, I recommend the first approach to create multiple data templates, as this is the most favorable from a perspective of maintenance and flexibility in my opinion.
I think it is better to use ItemsControl.ItemTemplateSelector if you like two datatemplates.
First you need one class inherit class "DataTemplateSelector" and override its method to select which datatemplate to use.
public class ModelItemTemplateSelector: DataTemplateSelector
{
public DataTemplate Model1Template { get; set; }
public DataTemplate Model2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if(item is Model1)
{
return Model1Template;
}
else if(item is Model2)
{
return Model2Template;
}
return base.SelectTemplate(item, container);
}
}
Then code in xaml is below
<ListBox ItemsSource="{Binding Source}">
<ListBox.ItemTemplateSelector>
<local:ModelItemTemplateSelector Model1Template="{StaticResource Model1Template}" Model2Template="{StaticResource Model2Template}" />
</ListBox.ItemTemplateSelector>
</ListBox>
And the other code:
Two datatemplates
<DataTemplate x:Key="Model1Template" DataType="{x:Type local:Model1}">
<TextBlock Text="{Binding Age}" />
</DataTemplate>
<DataTemplate x:Key="Model2Template" DataType="{x:Type local:Model2}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
Two types
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Model1 : BaseModel
{
private int age;
public int Age
{
get { return age; }
set
{
age = value;
this.RaisePropertyChanged(nameof(Age));
}
}
}
public class Model2 : BaseModel
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
this.RaisePropertyChanged(nameof(Name));
}
}
}
Source in vm
private ObservableCollection<BaseModel> source;
public ObservableCollection<BaseModel> Source
{
get { return source; }
set
{
source = value;
this.RaisePropertyChanged(nameof(Source));
}
}

Make attached properties work inside DataTemplate

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

WPF Dependency Property Bound To User Control

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>

Binding TabItem Header to Textblock in Style?

I'm trying to create a tab control in which the Header on the TabItem is binded in a textbox that is in the controltemplate of the tabcontrol. how can i do this through binding in the style?
Here's my code:
<Style x:Key="TabControlTest1" TargetType="TabControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--Area for TabItems-->
<Border Grid.Column="0"
Grid.Row="0"
>
<TabPanel IsItemsHost="True"
x:Name="HeaderPanel"
Background="Transparent" />
</Border>
<!--Content of SelectedItems-->
<Border Grid.Column="1"
BorderBrush="{DynamicResource TabControlContentPresentBorderBrush}"
BorderThickness="0,1,1,1"
Background="{DynamicResource TabControlContentPresentBackgroundBrush}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--///This is Where I want to bind the Header///-->
<Label
Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding Header, ElementName=TabItem}"/>
<ContentPresenter
Grid.Row="1"
x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"
Margin="5"/>
</Grid>
</Border>
<ControlTemplate x:Key="TabItemControlTemplate" TargetType="{x:Type TabItem}">
<!--Grid Defines Height and also hold content header-->
<Grid>
<Border Background="{DynamicResource TabItemContentPresentBackgroundBrush}"
Margin="0,0,0,5">
<!--Content of TabItem will be rendered-->
<ContentPresenter
x:Name="ContentSite"
Margin="3"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RecognizesAccessKey="True"
ContentSource="Header"/>
</Border>
</Grid>
</ControlTemplate>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
try this
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabControl}
, Path=SelectedItem.Header}"/>
You could try and achieve this either using TemplateBinding or using RelativeSource.
TemplateBinding:
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{TemplateBinding Header}"/>
RelativeSource:
<Label Grid.Row="0"
Foreground="AliceBlue"
Content="{Binding Path=Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
This isn't using a control template, but demonstrates binding a textbox to the tab header through a view model. Note that I'm using MVVM light (ViewModelBase and Set()), but you can replace with your own INotifyPropertyChanged support if necessary.
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type Samples:TabBindingViewModel}">
<StackPanel>
<TextBlock Text="{Binding MyContent}"/>
<TextBox Text="{Binding Header}"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<TabControl
ContentTemplate="{StaticResource ContentTemplate}"
DisplayMemberPath="Header"
ItemsSource="{Binding Items}" />
</Grid>
public class TabContentToHeaderViewModels : ViewModelBase
{
private readonly ObservableCollection<TabContentToHeaderViewModel> _items;
public TabContentToHeaderViewModels()
{
_items = new ObservableCollection<TabContentToHeaderViewModel>
{
new TabContentToHeaderViewModel(1),
new TabContentToHeaderViewModel(2),
new TabContentToHeaderViewModel(3),
};
}
public IEnumerable<TabContentToHeaderViewModel> Items
{
get { return _items; }
}
}
public class TabContentToHeaderViewModel : ViewModelBase
{
public TabContentToHeaderViewModel() : this(0)
{
}
public TabContentToHeaderViewModel(int n)
{
Header = "I'm the header: " + n.ToString(CultureInfo.InvariantCulture);
MyContent = "I'm the content: " + n.ToString(CultureInfo.InvariantCulture);
}
private string _header;
public string Header
{
get { return _header; }
set { Set(() => Header, ref _header, value); }
}
public string MyContent { get; set; }
}

WPF items not visible when grouping is applied

I'm having this strange issue with my ItemsControl grouping. I have the following setup:
<ItemsControl Margin="3" ItemsSource="{Binding Communications.View}" >
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemCount, StringFormat='{}[{0}] '}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Text="{Binding Name, Converter={StaticResource GroupingFormatter}, StringFormat='{}Subject: {0}'}" FontWeight="Bold" />
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock FontWeight="Bold" Text="{Binding Inspector, Converter={StaticResource NameFormatter}, StringFormat='{}From {0}:'}" Margin="3" />
<TextBlock Text="{Binding SentDate, StringFormat='{}{0:dd/MM/yy}'}" Grid.Row="1" Margin="3"/>
<TextBlock Text="{Binding Message }" Grid.Column="1" Grid.RowSpan="2" Margin="3"/>
<Button Command="vm:CommunicationViewModel.DeleteMessageCommand" CommandParameter="{Binding}" HorizontalAlignment="Right" Grid.Column="2">Delete</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my ViewModel, I expose a CollectionViewSource named 'Communications'. I proceed to adding a grouping patter like so:
Communications.GroupDescriptions.Add(new PropertyGroupDescription("Subject"));
Now, the problem i'm experience is the grouping work fine, but I can't see any items inside the groups. What am I doing wrong? Any pointers would be much appreciated.
I can't seem to reproduce the problem - I assume you are using a CollectionViewSource? It might be because you bound to the View property directly.
Here's the C# code I used:
public class Communication
{
public string Subject { get; set; }
public string Body { get; set; }
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var source = (CollectionViewSource)Resources["Communications"];
source.Source = new List<Communication>()
{
new Communication { Subject = "WPF 4.0", Body = "I love what's happening with 4.0"},
new Communication { Subject = "WPF 4.0", Body = "I hear the text rendering is the best feature"},
new Communication { Subject = "Blend 3.0", Body = "Behaviors in Blend 3 change everything"}
};
source.GroupDescriptions.Add(new PropertyGroupDescription("Subject"));
}
}
Here is the XAML - it's the same as yours but with a couple of things removed since I don't have your converters or commands:
<Window
x:Class="GroupStyleDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
>
<Window.Resources>
<CollectionViewSource x:Key="Communications" />
</Window.Resources>
<Grid>
<ItemsControl Margin="3" ItemsSource="{Binding Source={StaticResource Communications}}" >
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<Grid>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemCount, StringFormat='{}[{0}] '}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Text="{Binding Path=Name}" FontWeight="Bold" />
</Grid>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Body }" Grid.Column="1" Grid.RowSpan="2" Margin="3"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
After hitting this problem myself I discovered the cause: the template for ItemsControl directly including a panel with IsItemsHost="true".
You must insert an ItemPresenter into your template and set the ItemsControl.ItemsPanel property instead.

Resources