Dynamically change the content of of control based on a value - wpf

I want to achieve the following behavior:
Depending on a value use a different datatemplate:
<DataTemplate x:Key="cardTemplate2">
<Border x:Name="container">
.....
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ShowSecondDT}" Value="True">
<Setter Property="Child" TargetName="container">
<Setter.Value>
<StackPanel Orientation="Vertical" >
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
The application fails claiming that Setter Property="Child" is null...
Another information is that this Datatemplate in the resource of a control: (devexpress gris)
<dxg:GridControl xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
x:Name="gridTitulaire" DataSource="{Binding Contacts}" Width="600" >
<dxg:GridControl.Resources>
<DataTemplate x:Key="cardTemplate2">
<Border x:Name="container">
<StackPanel Orientation="Horizontal" >
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding isTitulairePrincipal}" Value="True">
<Setter Property="Child" TargetName="container">
<Setter.Value>
<StackPanel Orientation="Vertical" >
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</dxg:GridControl.Resources>
<dxg:GridControl.Columns>
<dxg:GridColumn FieldName="first_name"/>
<dxg:GridColumn FieldName="last_name"/>
</dxg:GridControl.Columns>
<dxg:GridControl.View>
<dxg:CardView x:Name="view" ShowGroupedColumns="True" CardTemplate="{DynamicResource cardTemplate2}" />
</dxg:GridControl.View>
</dxg:GridControl>
Any idea ?
Thanks
Jonathan

Define two separate data templates (call them CardTemplateV for the vertical one, and CardTemplateH for the horizontal one, for example), and set the CardTemplateSelector to a selector object that checks the discriminating field.
Example:
class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var cardData = item as CardData;
if (cardData == null) return null;
var dataObject = cardData.DataContext as YourDataType;
if (dataObject == null) return null;
if (dataObject.isTitulairePrincipal)
return (DataTemplate)App.Current.FindResource("CardTemplateV");
else
return (DataTemplate)App.Current.FindResource("CardTemplateH");
}
}
In the XAML add the selector to the resource dictionary and reference it from the grid:
<Window.Resources>
<local:YourTemplateSelector x:Key="MyTemplateSelector"/>
</Window.Resources>
...
<dxg:CardView
x:Name="view"
ShowGroupedColumns="True"
CardTemplateSelector="{StaticResource MyTemplateSelector}"/>
...

Related

Using togglebutton for RowDetailsTemplate

Say I want to use RowDetailsTemplate in my WPF.
<Grid x:Name="LayoutRoot" Background="White">
<data:DataGrid x:Name="grdVwDetails" AutoGenerateColumns="False" RowDetailsVisibilityMode="Collapsed">
<data:DataGrid.RowDetailsTemplate>
<DataTemplate>
<data:DataGrid HeadersVisibility="None" ItemsSource="{Binding Path= Project}" AutoGenerateColumns="True">
</data:DataGrid>
</DataTemplate>
</data:DataGrid.RowDetailsTemplate>
<data:DataGrid.Columns>
<data:DataGridTemplateColumn>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="+" Click="HandleExpandCollapseForRow"></Button>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
<data:DataGridTextColumn Header="EmployeeID" Binding="{Binding EmpID}"></data:DataGridTextColumn>
<data:DataGridTextColumn Header="Name" Binding="{Binding Name}"></data:DataGridTextColumn>
<data:DataGridTextColumn Header="Address" Binding="{Binding Address}"></data:DataGridTextColumn>
</data:DataGrid.Columns>
</data:DataGrid>
</Grid>
In code behind I use event to change the button appearance.
private void HandleExpandCollapseForRow(object sender, RoutedEventArgs e)
{
Button expandCollapseButton = (Button)sender;
DataGridRow selectedRow = DataGridRow.GetRowContainingElement(expandCollapseButton);
if (null != expandCollapseButton && "+" == expandCollapseButton.Content.ToString())
{
selectedRow.DetailsVisibility = Visibility.Visible;
expandCollapseButton.Content = "-";
}
else
{
selectedRow.DetailsVisibility = Visibility.Collapsed;
expandCollapseButton.Content = "+";
}
}
It seems work. My question is that I don't want to use +/- for button. I want to use different image for expand/collapse.
And do it should be done in xaml instead of code behind.
The entire project can be found at http://www.a2zmenu.com/Blogs/Silverlight/Collapse-RowDetailstemplate-on-clicking-again.aspx
That is for Silverlight, I use it for WPF.
You could replace the Button in the DataTemplate with a ToggleButton with a custom template:
<ToggleButton Width="50" Height="50">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Image x:Name="img" Source="plus.png" />
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="img" Property="Source" Value="minus.png" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>

WPF Binding to a RadioButton GroupName

I have a dynamic ListView using a DataTemplate as follows:
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="#ccc">
<RadioButton GroupName="MyRadioButtonGroup">
<StackPanel Orientation="Horizontal" Margin="0,0,0,5" Width="{Binding ActualWidth, ElementName=CheckoutGrid}">
<TextBlock Text="{Binding Path=Test1, StringFormat='{}{0} - '}" />
<TextBlock Text="{Binding Path=Test2, StringFormat='{} test {0} - '}" />
<TextBlock Text="{Binding Path=Test, StringFormat='{} test {0}'}" />
</StackPanel>
</RadioButton>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
I would like to have a button disabled until one of the RadioButton IsChecked:
<RadioButton Padding="15,10" VerticalAlignment="Center">
<RadioButton.Style>
<Style TargetType="RadioButton" BasedOn="{StaticResource styleToggleButton4}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyRadioButtonGroup, Path=IsChecked}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
<TextBlock FontWeight="SemiBold" FontSize="14" Margin="0">NEXT</TextBlock>
</RadioButton>
So the problem is I do not know how to properly bind to the RadioButton GroupName="MyRadioButtonGroup". You will see in the DataTrigger above I am trying Binding="{Binding ElementName=MyRadioButtonGroup, Path=IsChecked}" Value="False", but that is not working for me since obviously it is a GroupName and not an x:Name.
Any suggestions on how to approach this properly? I would rather want to handle this on the front-end if at all possible.
You could bind your radio button to a command and then use the isChecked as a parameter. Then bind to a boolean value based off that. I think this may be more of a workaround though.
<RadioButton
Command="{Binding MyCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}"
GroupName="radio" />
<RadioButton
Command="{Binding MyCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=IsChecked}"
GroupName="radio" />
Then in code behind or VM, it would require implementing the ICommand Interface and INotifyPropertyChanged
public bool IsChecked
{
get { return isChecked;}
set
{
isChecked = value;
OnPropertyChanged();
}
}
private void OnMyCommand(object obj)
{
if(obj is bool)
{
IsChecked = (bool)obj;
}
}
A group has no IsChecked property that you can bind to.
If you don't have a source property that keeps track of whether there is at least one checked RadioButton and "want to handle this on the front-end" - which I guess means in the view - you could write some code that simply sets the Tag property of the ListView to true/false depending on whether there is a RadioButton selected. It is a one-liner:
private void ListView_Checked(object sender, RoutedEventArgs e) => lv.Tag = true;
<ListView x:Name="lv" ToggleButton.Checked="ListView_Checked">
<ListView.ItemTemplate>
...
<ListView.ItemTemplate>
</ListView>
<RadioButton Padding="15,10" VerticalAlignment="Center">
<RadioButton.Style>
<Style TargetType="RadioButton" BasedOn="{StaticResource styleToggleButton4}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=lv, Path=Tag}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</RadioButton.Style>
<TextBlock FontWeight="SemiBold" FontSize="14" Margin="0">NEXT</TextBlock>
</RadioButton>

How can I vary the layout of a UserControl by a Property?

I've made the smallest project I can to demonstrate the problem:
Code behind:
public partial class AxisControl : UserControl
{
public static readonly DependencyProperty LayoutProperty =
DependencyProperty.Register("Layout", typeof(Orientation), typeof(AxisControl),
new PropertyMetadata(Orientation.Horizontal));
public Orientation Layout
{
get { return (Orientation)GetValue(LayoutProperty); }
set { SetValue(LayoutProperty, value); }
}
public AxisControl()
{
InitializeComponent();
}
}
Xaml:
<UserControl.Resources>
<ContentControl x:Key="horizontalLayout" Height="60">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource AncestorType=UserControl}}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</ContentControl>
<ContentControl x:Key="verticalLayout" Width="60">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource AncestorType=UserControl}}" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</ContentControl>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Setters>
<Setter Property="Content" Value="{StaticResource horizontalLayout}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Layout, ElementName=root}" Value="Vertical">
<Setter Property="Content" Value="{StaticResource verticalLayout}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Edit: Xaml now includes the elements I want to arrange in the UserControl.
MainWindow Xaml:
<Grid>
<local:AxisControl Layout="Vertical"/>
</Grid>
The idea is to set the layout of the UserControl according to its Layout property so I put both layouts in static resources and made a Style to set the Content to the one I want according to Layout which is of type Orientation.
Edit: I want the Content to include elements arranged in a different order according to the Orientation.
The UserControl displays correctly but there's an error in the output window that concerns me:
Cannot find source for binding with reference 'RelativeSource
FindAncestor, AncestorType='System.Windows.Controls.UserControl',
AncestorLevel='1''. BindingExpression:Path=Layout; DataItem=null;
target element is 'TextBlock' (Name=''); target property is 'Text'
(type 'String')
Does this mean it's trying to do a Binding before it's in the visual tree possibly from the Trigger?
Note the use of RelativeSource and ElementName in the Bindings as it's incorrect to set the DataContext at the root of a UserControl because it breaks the DataContext inheritance.
What am I doing wrong and how can I get rid of the error?
Inspired by Clemens comment and further research I realized I needed a ControlTemplate resource for each layout and not resources containing instances of the elements themselves.
<UserControl.Resources>
<ControlTemplate x:Key="horizontalLayout">
<Border Height="60" Background="LightBlue">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Border>
</ControlTemplate>
<ControlTemplate x:Key="verticalLayout" TargetType="UserControl">
<Border Width="60" Background="LightBlue">
<TextBlock Text="{Binding Layout, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Border>
</ControlTemplate>
</UserControl.Resources>
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Setters>
<Setter Property="Template" Value="{StaticResource horizontalLayout}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Layout, ElementName=root}" Value="Vertical">
<Setter Property="Template" Value="{StaticResource verticalLayout}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Layout = Horizontal
Layout = Vertical

Closing TabItem by clicking middle button

I have a problem.
In my WPF application, if i press a tabItem with middle mouse button, this tabItem should close. Just like in FireFox.
But I try to do this using MVVM, and i need to use commands. Also my tabItems are created dynamically.
Help me plz!
Thank you!
Create a DataTemplate for your tab items like this:
<DataTemplate x:Key="ClosableTabTemplate">
<Border>
<Grid>
<Grid.InputBindings>
<MouseBinding Command="ApplicationCommands.Close" Gesture="MiddleClick" />
</Grid.InputBindings>
<!-- the actual contents of your tab item -->
</Grid>
</Border>
</DataTemplate>
In your application window, add a the close command
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" CanExecute="CloseCommandCanExecute" />
</Window.CommandBindings>
and finally assign the data template as item template to your tab control.
You could add a MouseBinding to some InputBindings perhaps?
Closing a TabItem (Not Selected) - TabControl
<TabControl x:Name="TabControlUser" ItemsSource="{Binding Tabs}" Grid.RowSpan="3">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border Name="Border" BorderThickness="1,1,1,0" BorderBrush="Gainsboro" CornerRadius="4,4,0,0" Margin="2,0">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="10,2"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="LightSkyBlue" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="Border" Property="Background" Value="GhostWhite" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" FontWeight="ExtraBold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Image Source="/Images/RedClose.png" Width="22" Height="22" MouseDown="Image_MouseDown" HorizontalAlignment="Right" Margin="10 0 0 0"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<UserControl Content="{Binding UserControl, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
C# Code - Image MouseDown Click Event
try
{
int matches = 0;
DependencyObject dependency = (DependencyObject)e.OriginalSource;
// Traverse the visual tree looking for TabItem
while ((dependency != null) && !(dependency is TabItem))
dependency = VisualTreeHelper.GetParent(dependency);
if (dependency == null)
{
// Didn't find TabItem
return;
}
TabItem selectedTabItem = dependency as TabItem;
var selectedTabContent = selectedTabItem.Content as MainWindowViewModel.TabItem;
foreach (var item in MainWindowViewModel.Tabs)
{
if (item.Header == selectedTabContent.Header)
{
matches = MainWindowViewModel.Tabs.IndexOf(item);
}
}
MainWindowViewModel.Tabs.RemoveAt(matches);
}
catch (Exception ex)
{
System.Windows.Application.Current.Dispatcher.Invoke((System.Action)(() => new View.MessageBox(ex.Message).ShowDialog()));
}
This event finds with the exact Mouse clicked position (as TabItem)and this will remove
MainWindowViewModel.Tabs.RemoveAt(matches); the TabItem(even if it is not selected).

How can I use a custom TabItem control when databinding a TabControl in WPF?

I have a custom control that is derived from TabItem, and I want to databind that custom TabItem to a stock TabControl. I would rather avoid creating a new TabControl just for this rare case.
This is what I have and I'm not having any luck getting the correct control to be loaded. In this case I want to use my ClosableTabItem control instead of the stock TabItem control.
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True"
Controls:ClosableTabItem.TabClose="TabClosed" >
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type Controls:ClosableTabItem}" >
<TextBlock Text="{Binding Path=Id}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
EDIT: This is what I ended up with, rather than trying to bind a custom control.
The "CloseCommand" im getting from a previous question.
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border
Name="Border"
Background="LightGray"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="25,0,0,0"
SnapsToDevicePixels="True">
<StackPanel Orientation="Horizontal">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="20,1,5,1"/>
<Button
Command="{Binding Path=CloseCommand}"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
Margin="1,1,5,1"
Background="Transparent"
BorderThickness="0">
<Image Source="/Russound.Windows;component/Resources/Delete.png" Height="10" />
</Button>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter TargetName="Border" Property="Background" Value="LightBlue" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter TargetName="Border" Property="BorderBrush" Value="DarkBlue" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
found a way,
derive a class from TabControl and override this function, in my case I want the items of the tab control (when bound) to be CloseableTabItems
public class CloseableTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new CloseableTabItem();
}
}
HTH Someone
Sam
You don't want to set the DataType of the DataTemplate in this case. The value of the ItemTemplate property is used whenever a new item needs to be added, and in the case of a tab control it will be used to create a new TabItem. You should declare an instance of your class within the DataTemplate itself:
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True" Controls:ClosableTabItem.TabClose="TabClosed">
<TabControl.ItemTemplate>
<DataTemplate>
<Controls:ClosableTabItem>
<TextBlock Text="{Binding Path=Id}" />
</Controls:ClosableTabItem>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This will cause a new ClosableTabItem to be created whenever a new tab is added to the TabControl.
Update; From your comment, it sounds like that the ItemTemplate controls what is created within the TabItem, rather than changing the TabItem itself. To do what you want to do, but for a TreeView, you would set the HeaderTemplate. Unfortunately, I don't see a HeaderTemplate property of TabControl.
I did some searching, and this tutorial modifies the contents of the tab headers by adding controls to TabItem.Header. Maybe you could create a Style for your TabItems that would add the close button that your class is currently adding?

Resources