WPF TemplateBinding to DataContext of templated parent - c#

We have four identical popups with grids in four XAML views. I'd like to move that XAML to a template and apply via a Style to to ContentControls in all four of them. The trouble is passing in the source of the items in the grids. We get that from each of four different view models. It's different in each case, the only thing that differs among the four cases. I'll probably end up renaming them consistently, but I'd like to think that's a separate issue.
Obviously I don't understand TemplateBinding at all. How do I bind a property of a child of the template to a property of the ContentControl that I'm applying the template to?
Except for the value of the DataSource attribute changing, the XAML for the grid is identical to what works perfectly well when we use it directly.
I added the TextBlock just to see if I could bind anything at all. I do get NaN there.
<Style x:Key="HistoryPopupContentStyle" TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{TemplateBinding Width,
diag:PresentationTraceSources.TraceLevel=High}"
Background="White"
Foreground="Black"/>
<dxg:GridControl
DataSource="{Binding RelativeSource={RelativeSource
Path=DataContext,
TraceLevel=High}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
>
<!-- Columns. The grid displays column headers
as desired but with no rows -->
</dxg:GridControl.Columns>
</dxg:GridControl>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Popup
Name="PopHistory"
DataContext="{Binding Path=HistoryList}"
>
<ContentControl DataContext="{Binding Path=HistoryList}"
Style="{StaticResource HistoryPopupContentStyle}"
Name="Testing"
/>
</Popup>

You will need to subclass ContentControl (or just Control), so that you can add new dependency properties.
public class GridControl : ContentControl
{
// TODO add dependency properties
public GridControl()
{
DefaultStyleKey = typeof(GridControl);
}
}
Add your "Items" dependency property to the above control (type IEnumerable).
Next, update your template to target the new type:
<Style x:Key="HistoryPopupContentStyle" TargetType="local:GridControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<dxg:GridControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:GridControl},Path=Items}" />
Alternately, you could set the "Template" instead of the "ContentTemplate". This would be when you use TemplateBinding:
<Style x:Key="HistoryPopupContentStyle" TargetType="local:GridControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:GridControl">
<dxg:GridControl ItemsSource="{TemplateBinding Items}" />
Use it by binding the Items property to your source items:
<local:GridControl Style="{StaticResource HistoryPopupContentStyle}"
Items="{Binding Path=HistoryList}" />
You could also skip creating a subclass altogether, and just use the Content property of ContentControl to stash the items:
<Style x:Key="HistoryPopupContentStyle" TargetType="ContentControl">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<dxg:GridControl ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:GridControl},Path=Content}" />
Or using the Template / TemplateBinding approach
<Style x:Key="HistoryPopupContentStyle" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<dxg:GridControl ItemsSource="{TemplateBinding Content}" />
Use like this:
<ContentControl Style="{StaticResource HistoryPopupContentStyle}"
Content="{Binding Path=HistoryList}" />

Related

TabControl.Resource DataTemplate Use Icon Instead of Text

I have a project that adds tabs to the view using a TabControl with DataTemplates like so:
<TabControl Name="dcTabControl"
ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTabViewModel}"
Height="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.MainContentHeight}">
<TabControl.Resources>
<!-- Removed numerous other tabs to save space -->
<!-- System Setup tab -->
<DataTemplate DataType="{x:Type vm:SystemSetupViewModel}">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<v:SystemSetupUserControl />
</ScrollViewer>
</DataTemplate>
<!-- About tab -->
<DataTemplate DataType="{x:Type vm:AboutViewModel}">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<v:AboutUserControl />
</ScrollViewer>
</DataTemplate>
</TabControl.Resources>
</TabControl>
Each ViewModel has a Header property that is used to populate the text of the tab (e.g. "About").
I now have a requirement to change the "About" text to an icon. I have tried this but it doesn't change anything.
<!-- About tab -->
<DataTemplate DataType="{x:Type vm:AboutViewModel}">
<TabItem>
<TabItem.Header>
<Image Name="AboutTabImage" Height="auto" Width="auto" Source="Images/About.png" />
</TabItem.Header>
<TabItem.Content>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<v:AboutUserControl />
</ScrollViewer>
</TabItem.Content>
</TabItem>
</DataTemplate>
How can I get an icon in place of the text?
UPDATE Adding code to show how Header property is bound to Tab.
The Header is bound to the Tab using this Style
<!-- Standard Tab Style -->
<Style x:Key="TabStyle" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Padding" Value="10,5,10,5" />
</Style>
I am now thinking I have to create a new style to use an icon instead of text, but not sure how I would apply that style to the data template.
You may add a DataTrigger to the TabItem Style that changes the Header to an Image if the Header property contains the string "About":
<Style TargetType="TabItem">
...
<Setter Property="Header" Value="{Binding Header}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Header}" Value="About">
<Setter Property="Header">
<Setter.Value>
<Image Source="Images/About.png"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>

WPF MVVM Binding dynamic control in code behind and pass in View

I am working on WPF application using MVVM. I have two page. I have multiple UserControls in a page 1, on selection of UserControls from page 1, I want to show that selected userControl in 2nd page. Below are my code.
ViewModel Code
public RelayCommand<string> OnClickSelectWidgetCommand => new RelayCommand<string>((setUserControlName) =>
{
using (new CursorWait())
{
var MyContentControl = setUserControlName;
MessageBox.Show(MyContentControl);
//How to render UserControl to View?
}
}, true);
Here in above code I get the UserControl name in setUserControlName variable. Now how to bind that UserControl to XAML page? Below are my code that I have tried.
View Code
<StackPanel Background="Black" VerticalAlignment="Top">
<Border Name="UserControl1BorderLow" BorderBrush="White" BorderThickness="0" >
<ItemsControl ItemsSource="{Binding LowCollection}" Margin="4,0" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn1XL HorizontalAlignment="Left" Margin="2" />
<!--what can I do here in above line to make it dynamically render the userControl in place of UserControlColumn1XL-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border></StackPanel>
Above code, In DataTemplate what need to be change to bind UserControls dynamically?
There are two ways to solve this, one involves setting the template based on your data type (DataTemplates) and the second involves setting it based on the data itself (DataTriggers).
In the first case your LowCollection should be an array of objects, or some base class that your view models are all derived from (ViewModel1, ViewModel2 etc). In this case you can get rid of your itemtemplate altogether and just add DataTemplates to specify how each of the items in your ItemsControl should be represented:
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<UserControl1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<UserControl2 />
</DataTemplate>
... etc...
In the second case you need to set a template based on the value of some property in your view model. In this case you do need to set the ItemTemplate, and you give it a Style which uses data triggers to set an appropriate DataTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue1">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue2">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</DataTemplate>
</ItemsControl.ItemTemplate>
The relevant parts to note here are that there is a property in your view model called YourProperty which can have two values i.e. YourValue1 or YourValue2; the style above then selects either YourDataTemplate1 or YourDataTemplate2, depending on the value of YourProperty.

Binding in Style in Silverlight 5

I know that Silverlight 5 introduces the data binding in styles. I want to bind the source of image which is present in content template in the style of a button.
I am using the below code where I am trying to set the image source property in style.
// Style
<UserControl x:Class="MGPIControls_Simple.ButtonControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
Height="40" Width="40"
mc:Ignorable="d" x:Name="ButtonControlSample">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.Resources>
<Style x:Key="ImageButtonStyle" TargetType="Button">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- binding in style -->
<Image Source="{Binding ImageSource}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Stretch="Fill"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Button x:Name="ButtonBase" Style="{StaticResource ImageButtonStyle}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Grid>
Where ImageSource is the dependency property I have created. If I dont bind the image source property and keep it static to some image url the things are working fine but binding is not working. Please let me know where I am wrong in above approach.
You have to use binding like
<TextBlock Text="{Binding Path=DataContext.BusyText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
Well, how to put this... what you try to do is not the new Silverlight 5 feature Binding in Styles. This kind of binding is always possible, even with older Silverlight versions.
You have a DataTemplate and that means any binding you declare is evaluated when actual UI elements are instantiated from the template. And your binding Source="{Binding ImageSource}" is evaluated against your Button's DataContext.
If there is no public property ImageSource then your Button won't show any image.

ListBox , DataTemplate and Triggers

i got a DataTemplate for a listboxitem and i want to create a triger , so when a user click an item the background will change and also the label
my code:
<Window.Resources>
<Style x:Key="RoundedItem" TargetType="ListBoxItem">
<EventSetter Event="MouseDoubleClick" Handler="listViewItem_MouseDoubleClick" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="ItemBorder" CornerRadius="10" BorderBrush="Black" BorderThickness="1" Margin="1" Background="Transparent">
<Label Name="ItemLabel" Foreground="Red" >
<ContentPresenter />
</Label>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="ItemBorder" Property="Background" Value="DeepSkyBlue" />
<Setter TargetName="ItemLabel" Property="Foreground" Value="Orange" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="TitleTemplate" DataType="models:Title" >
<StackPanel>
<Image Source="{Binding ThumbFilePath}" Width="50" HorizontalAlignment="Center"/>
<Label Content="{Binding Name}" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Description}" HorizontalAlignment="Center" TextWrapping="Wrap" Padding="5,5,5,5"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
What happend is that the TextBlock change his color and not the label..
anyone know why ?
Thanks.
The TextBlock inherits the Foreground definition from its parents in the visual tree. The Label, on the other hand, defines the Foreground in its default style.
Your approach is "non-WPF-like" - you shouldn't wrap the ContentPresenter in a Label control.
The right approach depends on whether you want all text in the item to change its Foreground, or just the label?
[In both cases, there's no apparent benefit to using a Label in the data template - so I'll assume that the label is changed to TextBlock.]
If the answer to the above question is that all text should be changed: in the ControlTemplate of the ListBoxItem, in the trigger for IsSelected, from the seccond setter remove TargetName="ItemLabel" so the final setter is:
<Setter Property="Foreground" Value="Orange" />
This will change the foreground of the item that will affect the foreground of both TextBlocks in the data template.
If you want to affect just one of the TextBlocks:
1. remove the setter for the foreground from the control template
2. add a trigger to your data template:
<DataTemplate>
<StackPanel>
<Image .../>
<TextBlock x:Name="Text01" ..../>
<TextBlock x:Name="Text02" ..../>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" Value="True">
<Setter TargetName="Text01" Property="Foreground" Value="Orange"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Side note: if you have to use Label control in your data template, bind its Foreground property to the Foreground of the list box item, like so:
<Label Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"....../>
If this doesn't help, it means that your list box item inherits its foreground, so use:
<Label Foreground="{Binding TextElement.Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"....../>
I want to tack on to this that I was experiencing a similar problem where I'd added a ListBox.ItemTemplate to my ListBox, and the styling then did not apply to the text anymore.
What I was doing was trying to display a list of languages (CultureInfo) for the user to select from, however I wanted the native names to display, not the English name. For some reason, not all languages have their native names capitalized in their CultureInfo, and NativeName is the only instance of their name, so I needed to apply a function to the CultureInfo.NativeName to capitalize the names myself. To accomplish this, I added the ItemTemplate with a Data Template inside, on which I applied a converter.
<ListBox IsSynchronizedWithCurrentItem="True" VerticalAlignment="Center" MinHeight="200" x:Name="cbLanguages"
ItemsSource="{Binding Path=SupportedCultures, Mode=OneWay, Source={StaticResource CultureResourcesDS}}"
FontSize="24" HorizontalAlignment="Stretch" Width="300" Margin="10"
Style="{DynamicResource ListBoxTemplate}" ItemContainerStyle="{DynamicResource ListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Converter={StaticResource NativeNameConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
After a while of searching, I came across XAMeLi's answer here, and changed the Label I'd put in the DataTemplate to a TextBox, and the ListBoxItemStyle I'd created worked again.
Basically, Labels and TextBoxes have different traits that can be exploited, or can cause annoying issues in this case. Here's a good explanation with some examples of the differences: http://joshsmithonwpf.wordpress.com/2007/07/04/differences-between-label-and-textblock/

Binding the IsSelectedProperty of a ListBox in WPF is not working. Need Help

I am trying to do something that should be brain-dead simple, however, I cannot get it to work. I am displaying a list of items in a listbox. I have added check boxes to the list box so that the user can select multiple items. However, even though the object in the list being bound to the ListBox has an "IsSelected" property, it is not being bound. I could use some help as this is driving me nuts.
<Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple"></Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Margin" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<CheckBox Focusable="False"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<ContentPresenter></ContentPresenter>
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<ListBox
Style="{StaticResource CheckBoxListStyle}"
IsEnabled="{Binding Path=SpecificClients.Value, Mode=OneWay}"
ItemsSource="{Binding Path=SelectedClients}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.VerticalScrollBarVisibility="Auto"
MaxHeight="95">
</ListBox>
In the View Model I have the following:
public IEnumerable<SelectedClientVM> SelectedClients
....
public class SelectedClientVM
{
public bool IsSelected { get; set; }
public Client Client { get; set; }
public override string ToString()
{
return Client.SearchText;
}
}
I think what you want can be better achieved by defining a DataTemplate to be used for each item in the ListBox. A DataTemplate specifies how you want an individual piece of data (a Client in your case) rendered in the ListBox.
Here's my XAML for a simple DataTemplate.
<DataTemplate x:Key="clientTemplate" DataType="{x:Type local:Client}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding IsSelected}" />
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="5,0,0,0" />
</Grid>
</DataTemplate>
Here's how I referenced it in the ListBox declaration:
<ListBox ItemsSource="{Binding SelectedClients}"
VirtualizingStackPanel.IsVirtualizing="True"
ItemTemplate="{StaticResource clientTemplate}" />
Secondly, to Grant's answer, you'll want to be sure that your Client class implements INotifyPropertyChanged. Plus, you'll want to expose your list of Clients using a collection that supports change notifications. I usually use ObservableCollection<T>.
This may not be the only issue, but if you want the view to update based on your ViewModel you'll have to implement INotifyPropertyChanged (or something that does a similar job) on your IsSelected property.

Categories

Resources