How to handle events inside a WPF DataTemplate? - wpf

I have a custom control which has a dependency property called ViewModel which value is shown inside a ContentPresenter. I have a DataTemplate for each type of ViewModel. Each template allows the user to make a selection in a different way and I need to handle that selection event in the custom control code behind.
<Style TargetType="{x:Type local:MyCustomControl}">
<Style.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:ViewModelOne}">
<!-- how to handle this event? -->
<ListBox
MouseDoubleClick="ListBox_MouseDoubleClick"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelTwo}">
<!-- this ListBox has another style, but event should
be handled the same way -->
<ListBox
MouseDoubleClick="ListBox_MouseDoubleClick"/>
</DataTemplate>
<!-- more templates here -->
</ResourceDictionary>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<ContentPresenter Content="{TemplateBinding ViewModel}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit:
Here's the code behind of the custom control with the method I would like to be called when something in the ListBox is double clicked:
public class MyCustomControl : Control
{
// how to attach ListBox MouseDoubleClick event to this method?
private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
DoMagic(((ListBox)sender).SelectedItem);
}
}

Are these DataTemplates defined in a resource dictionary?
if so, you can use the attached behaviors.
If they are defined in a MyWindow or a MyUserControl XAML then you can defined them the code behind i.e. MyWindow.xaml.cs or MyUserControl.xaml.cs

Related

WPF Custom Control Display the Content

I'm creating a custom control in WPF, and I would like to be able to display whatever I put inside it.
A good example would be a Grid, StackPanel, DockPanel
Where you may do something like:
<StackPanel>
<TextBox />
<Button/>
</StackPanel>
And the StackPanel knows about the TextBox and the Button displays them and reacts accordingly.
Question:
How can I display what I put inside my control?
I would like to be able to do something like:
<controls:MyControl>
<Grid>
<TextBox />
<Button />
</Grid>
</controls:MyControl>
Update
Code behind looks like:
public class MyControl:ContentControl
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl),
new FrameworkPropertyMetadata(typeof(MyControl)));
}
}
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Extend ContentControl:
public class MyControl : ContentControl {}
It has a Content property that you can set to any object, including a Grid or any other Panel.

How to change the controle depending upon the data type in WPF

I have to Retrieve values from DB using WCF service and fill a dropdown named “Type” with ID-Type values (Use observable collection to bind this).
There should be another control controlled by Data Template/control template, which will be displayed depending upon Type selected. e.g. if TextBox type is selected then TextBox should be displayed with some default value.
InputType Text box – This will be used to create new type in DB. Use Save button to save value.
Delete Button – This should delete the selected Type from DataBase.
I am done with the DataBase Stuff and all , but how should i change the controle depending upon the data type in XAML ?
You can use a general ContentControl with a style that will select (via Triggers) different ControlTemplates which contain the appropriate control types.
This approach can also be slightly modified to use DataTemplates instead of ControlTemplates (arguably a better approach). Instead of setting the Template property (which is a ControlTemplate), set the ContentTemplate property (which is a DataTemplate) and fill each DataTemplate with your desired control/s.
<Window x:Class="ControlTypeBasedOnComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0"
ItemsSource="{Binding Path=ControlTypes}"
x:Name="ControlTypeComboBox"/>
<ContentControl Grid.Row="1">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ControlTypeComboBox, Path=SelectedItem}" Value="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<TextBox/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ControlTypeComboBox, Path=SelectedItem}" Value="CheckBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<CheckBox/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ControlTypeComboBox, Path=SelectedItem}" Value="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Button/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
The code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
The View Model:
public class ViewModel
{
ObservableCollection<string> controlTypes;
public ViewModel()
{
controlTypes = new ObservableCollection<string>() { "TextBox", "CheckBox", "Button" };
}
public ObservableCollection<string> ControlTypes
{
get { return controlTypes; }
}
}
As for the save/delete button, you can also bind the Command properties to different ICommand objects on your View Model based on the SelectedItem of the ComboBox. I don't know exactly what kind of functionality you need, so I don't know if that's necessary/appropriate.
Hope that helps!

How to bind to the control's dependency property inside ControlTemplate

I've extended PopupBaseEdit control by adding my own ControlTemplate and a new DependencyProperty PopupText to the extended control. The property PopupText is initialized from the control's consumer. No issues here. The question is; how do I bind TextBox.Text to my attached property inside ControlContent?
Here is the XAML:
<ControlTemplate x:Key="myPopuptemplate">
<StackPanel>
<TextBox Margin="5" Text="???????"/>
</StackPanel>
</ControlTemplate>
<Style TargetType="local:myControl">
<Setter Property="PopupContentTemplate"
Value="{StaticResource myPopuptemplate}"/>
</Style>
Use a TemplateBinding.

ControlTemplate not being applied inside of DataTemplate

I have an object graph which shows a number of items. I want to handle the "Click" event on those items such that a detail view can be updated to show the last clicked item.
I'm attempting to do this with a ControlTemplate which will be applied to the DataTemplates in the object graph. The markup looks like this:
<ContentControl Background="White">
<ContentControl.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type local:GraphableType}">
<Button>
<!-- visualization of the GraphableType -->
</Button>
</DataTemplate>
</ContentControl.Resources>
<ContentControl>
The ControlTemplate isn't applied to the DataTemplate (it shows nothing). If I set the Button.Template property inside the DataTemplate it does seem to work though. Any ideas?

Specify ControlTemplate for ItemsControl.ItemContainerStyle

The following is similar to what I'm trying to accomplish. However, I get the error
Invalid PropertyDescriptor value.
on the Template Setter. I suspect it's because I didn't specify a TargetType for the Style; however, I don't know the container type for ItemsControl.
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<TextBlock Text="Some Content Here" />
<ContentPresenter />
<Button Content="Edit" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<!-- heterogenous controls -->
<ItemsControl.Items>
<Button Content="Content 1" />
<TextBox Text="Content 2" />
<Label Content="Content 3" />
</ItemsControl.Items>
</ItemsControl>
You can qualify the property name with the type name:
<Setter Property="Control.Template">
The container for ItemsControl is normally a ContentPresenter, but if the child is a UIElement then it won't use a container. In this case, all of the children are Controls, so the ItemContainerStyle will apply to them directly. If you added an item other than a UIElement, that setter would set the Control.Template property on the ContentPresenter, which would succeed but have no effect.
Actually, it sounds like what you want is to wrap each child in a container, even if they are already a UIElement. To do that, you will have to use a subclass of ItemsControl. You could use an existing one like ListBox, or you could subclass ItemsControl and override GetContainerForItemOverride and IsItemItsOwnContainerOverride to wrap the items in your own container. You could wrap them in a ContentControl and then use that as the TargetType for the Style.
public class CustomItemsControl
: ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ContentControl();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
// Even wrap other ContentControls
return false;
}
}
You will also need to set the TargetType on the ControlTemplate so that the ContentPresenter will bind to the Content property:
<ControlTemplate TargetType="ContentControl">
Also if you only want to do all of it with XAML you can simply use ListBox instead of ItemsControl and define a style for ListBoxItem:
<ListBox ItemsSource="{Binding Elements.ListViewModels}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel>
<TextBlock>Some Content Here</TextBlock>
<ContentPresenter Content="{TemplateBinding Content}" />
<Button>Edit</Button>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Note that because I am using ListBox the container is ListBoxItem(Generally the container for WPF's default list control is always named the Item) so we create a style for ListBoxItem:
<Style TargetType="ListBoxItem">
Then we create a new ControlTemplate for ListBoxItem. Please note that ContentPresenter is not used as it always appears in articles and tutorials, you need to template-bind it to Content property of ListBoxItem, so it will show the content for that item.
<ContentPresenter Content="{TemplateBinding Content}" />
I just had the same problem and fixed it this way. I dont wanted some functionalities of ListBox ( item selection ) and by using this technique the item selection does not work anymore.

Resources