Custom control OnApplyTemplate called after dependency property callback - wpf

I'm developing my first WPF custom control and I'm facing some problems, here's a simplified version of the code I'm currently using:
using System.Windows;
using System.Windows.Controls;
namespace MyControls
{
[TemplatePart(Name = "PART_Button", Type = typeof (Button))]
public class MyControl : Control
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof (object), typeof (MyControl), new PropertyMetadata(null, OnLabelPropertyChanged));
private Button _buttonElement;
public object Content
{
get { return this.GetValue(LabelProperty); }
set { this.SetValue(ContentProperty, value); }
}
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof (MyControl), new FrameworkPropertyMetadata(typeof (MyControl)));
}
private static void OnContentPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyControl myControl = sender as MyControl;
if (myControl != null && myControl._buttonElement != null)
myControl._buttonElement.Content = e.NewValue;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this._buttonElement = this.Template.FindName("PART_Button", this) as Button;
}
}
}
This is the template for my custom control:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControls">
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyControl}">
<Button x:Name="PART_Button" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Then i put it inside a Grid and try to set its Content property:
<Grid x:Name="layoutRoot">
<controls:MyControl x:Name="myControl" />
</Grid>
Here's the code behind:
using System.Windows;
namespace MyControls
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.myControl.Content = "test";
}
}
}
This doesn't work, for some reason the OnContentPropertyChanged callback is called before OnApplyTemplate, so myControl._buttonElement is assigned too late and it's still null when trying to set its content. Why is this happening and how can I change this behavior?
I also need to provide full design time support but I cannot find a way to make my custom control accept some additional markup, much like the Grid control does with ColumnDefinitions:
<Grid x:Name="layoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
Any help would be greatly appreciated!
UPDATE
I found a document that explains why the OnApplyTemplate method is called after control properies are set:
http://msdn.microsoft.com/en-us/library/dd351483%28v=vs.95%29.aspx
So the question is: how can I keep track of the properties that are set (in XAML or programmatically) and methods that are called when the control has not been initialized, so that I can set/call them when the OnApplyTemplate method is called? How can the same callback/method work both before and after the control initialization without duplicating the code?

UPDATE:
Instead of your "property" pushing changes in value into elements in your template by looking for Parts, you should instead have your template bind to properties on the control being templated.
Normally this is done using presenters within a template e.g. ContentPresenter binds to the property designated as the "content" (it finds out that name by looking for the [ContentProperty] attribute), and by using bindings in your template that use TemplateBinding or TemplatedParent to connect to the properties on your custom control.
Then there is no issue about what order you set your properties and when the template is applied....because it is the template that provides the "look" for the data/properties set on your control.
A custom control should only really need to know and interact with "parts" if it needs to provide certain behaviour/functionality e.g. hooking the click event on a button "part".
In this case instead of setting the Content in the constructor in code-behind, you should get your template to bind to the property. The example I gave below showed how that was generally done with a Content property.
Alternatively you could pull out properties more explicitly e.g. this could be inside your template.
<Label Content="{TemplateBinding MyPropertyOnMyControl}" .....
<Button Content="{TemplateBinding AnotherPropertyOnMyControl}" .....
I think it would be better to designate your "content" using [ContentProperty] attribute, and then using a ContentPresenter in your template so that it can be injected inside your Button, rather than you hooking your Content DependencyProperty. (if you inherit from ContentControl then that provides the "content" behaviour).
[TemplatePart(Name = "PART_Button", Type = typeof (Button))]
public class MyControl : Control
[ContentProperty("Content")]
and
<ControlTemplate TargetType="{x:Type local:MyControl}">
<Button x:Name="PART_Button">
<ContentPresenter/>
</Button>
</ControlTemplate>
As for you wanting to be able to specify some design time data via XAML like Grid does with ColumnDefinition....well that is just using Property Element syntax to specify the items to fill an IList/ICollection typed property.
So just create your own property that can hold a collection of the type you accept e.g.
public List<MyItem> MyItems { get; set; } // create in your constructor.

I have encountered similar problem, OnApplyTemplate was called before OnLoaded.
The problem was due to binding in xaml. I bound boolean property to Checked instead of IsChecked on one of my checkboxes.

Related

How to apply a style to a DependencyObject in a custom control library

I am creating a reusable custom control, based on the TreeView. I have on the custom control created a dependency property for the columns in the control, like this:
public GridViewColumnCollection Columns
{
get { return (GridViewColumnCollection)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListView), new PropertyMetadata(new GridViewColumnCollection()));
This lets me specify a bunch of columns in XAML. The catch is that I need the first column to have a custom cell template. I was going to approach this by deriving a class from GridViewColumn, something like this:
public class TreeGridViewColumn : GridViewColumn
{
}
and then give it the desired style in the Generic.xaml for the custom control:
<Style TargetType="{x:Type local:TreeGridViewColumn}">
<Setter Property="CellTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="Black" /> <!-- Just for example -->
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
However the style is never applied to instances of TreeGridViewColumn. I know that I probably need to add:
DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeGridViewColumn), new FrameworkPropertyMetadata(typeof(TreeGridViewColumn)));
However I cannot do this, as the GridColumn base class is not a FrameworkObject, it is a DependencyObject. How can I apply a style to a descendant of a GridViewColumn defined in a Custom Control library?
Think like this: The TreeGridViewColumn should be a dummy object holding important information for the column itself such as width and height and also for each cell under that columns header for example the cell template itself. Therefore do not try to create an FrameworkElement out of TreeGridViewColumn. Here is an example how you might end up using the TreeGridViewColumn.
<TreeGridViewColumn Header="First Col" Width="50">
<TreeGridViewColumn.CellTemplate>
<DataTemplate>
<Button>
click me
</Button>
</DataTemplate>
</TreeGridViewColumn.CellTemplate>
</TreeGridViewColumn>
Once you ready to display the columns and cells I suggest to you to write your own custom panel which deals with the FrameworkElements by calling their Measure and Arrange methods allowing you to position columns and cells the way you want. You will end up doing alot of math inside your custom panel class. That futhermore means you will end up spending a month on programming that TreeGridView. I suggest you to take a shortcut and download the code of such a thing. There are already few TreeListViews online. Just take their dlls and see if it will work out for you
EDIT:
Ok here is a suggestion how you could solve your issue. Its just a suggestion
The DefaultTextColumnData class is a dummy object holding all the necessary infos like columns width, etc.
DataGridCellControl will be that FrameworkElement that draws the cell. Its a FrameworkElement so it will have a defined style in your generic.xaml resource dictionary.
To sum up DefaultTextColumnData will hold all infos for the column itself. DataGridCellControl will be a control which might end up having 20 instances of itself in case you have 20 cells in that column.
DataGridCellControl must know about its column.
This is how the code of DataGridCellControl will look alike:
class DefaultTextColumnData : DataGridColumn
{
}
class ComplexColumnData : DataGridColumn
{
}
class DataGridCellControl : Control
{
public DataGridColumn Column
{
get; set;
}
public DataTemplate DefaultTextCellTemplate
{
get; set;
}
public override Size MeasureOverride(Size size)
{
...
if(this.Column is DefaultTextColumnData)
{
this.Template = this.DefaultTextCellTemplate
}
if(this.Column is ComplexColumnData)
{
this.Template = ...
}
...
return new Size(30, 30);
}
}
DefaultTextCellTemplate will be set in your generic.xaml like this:
<Style TargetType={x:Type DataGridCellControl}>
<Setter Property="DefaultTextCellTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Background="Black" Margin="5"/>
....
Thats how you set default cell template in your resource dictionary.

x:Name not working if element wrapped in UserControl's content (Silverlight)

Situation:
I have a "wrapper panel" UserControl like this (namespaces and visual details removed for brevity):
<UserControl ...>
<Grid x:Name="LayoutRoot" Background="White">
<ContentPresenter x:Name="integratedPanelContent" Margin="5" />
</Grid>
</UserControl>
Then in the Code-behind I have registered a dependency property
public FrameworkElement PanelContent
{
get { return (FrameworkElement)GetValue(PanelContentProperty); }
set { SetValue(PanelContentProperty, value); }
}
public static readonly DependencyProperty PanelContentProperty =
DependencyProperty.Register("PanelContent", typeof(FrameworkElement), typeof(MyWrapperPanel),
new PropertyMetadata(null, OnPanelContentChanged));
private static void OnPanelContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyWrapperPanel)d).OnSetContentChanged(e);
}
protected virtual void OnSetContentChanged(DependencyPropertyChangedEventArgs e)
{
if (PanelContent != null)
integratedPanelContent.Content = PanelContent;
}
Now I can wrap any content into my control:
<my:MyWrapperPanel x:Name="myWrap">
<my:MyWrapperPanel.PanelContent>
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel.PanelContent>
</my:MyWrapperPanel>
Description of the problem:
Whenever I try to use the reference tbxNothing in codebehind, the system throws NullReferenceException because tbxNothing, although as a reference exists, does not point to the TextBlock defined in XAML, but is null.
Possible (but inconvenient) workaround:
There is a workaround where I remove x:Name from the TextBlock, and then I explicitely define private TextBlock called tbxNothing. Then in the OnNavigatedTo event handler I assign the value the following way:
tbxNothing = myWrap.PanelContent as TextBlock;
This works but is not a right way to do it, because if a content is a stackpanel that contains wanted controls, I'd have to traverse the tree to find what I need, which is extremely inconvenient.
Question:
Why is the textblock no longer visible when wrapped in a User control (the way described), and how to get it by its x:Name in code-behind?
The problem is your panel content is falling between two stools. On the one hand the content with the name "tbxNothing" is create in the namescope of the main page. However its not added to the object tree at that point. On the other hand the MyWrapperPanel being a UserControl has its own namescope and its into the object tree below this that the item with then name "tbxNothing" is added. FindName on the main page won't find anything inside the MyWrapperPanel because it has its own namescope and FindName on the MyWrapperPanel won't find "tbxNothing" because it doesn't exist in its namescope (being actually created in the main page).
The answer is don't use a UserControl as a basis for MyWrapperPanel. Instead create a Silverlight Template Control. Modify the base class it inherits from to ContentControl and tweak its default template to include a ContentPresenter. Should look something like this:-
public class MyWrapperPanel : ContentControl
{
public MyWrapperPanel ()
{
this.DefaultStyleKey = typeof(MyWrapperPanel );
}
}
then in themes/generic.xaml the style can look like this:-
<Style TargetType="local:MyWrapperPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyWrapperPanel">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Your main page xaml would look like:-
<my:MyWrapperPanel x:Name="myWrap">
<TextBlock x:Name="tbxNothing" Text="Nothing" />
</my:MyWrapperPanel>
Note that deriving from ContentControl gives you a Content property which the ContentPresenter auto-magically wires to.

WPF - Events on a ControlTemplate?

Does anyone know why I can't set an event on a control template??
For example, the following line of code will not compile. It does this with any events in a control template.
<ControlTemplate x:Key="DefaultTemplate" TargetType="ContentControl">
<StackPanel Loaded="StackPanel_Loaded">
</StackPanel>
</ControlTemplate>
I am using a MVVM design pattern and the control here is located in a ResourceDictionary that is added to the application's MergedDictionaries.
Does anyone know why I can't set an event on a control template??
Actually, you can... But where would you expect the event handler to be defined ? The ResourceDictionary has no code-behind, so there is no place to put the event handler code. You can, however, create a class for your resource dictionary, and associate it with the x:Class attribute :
<ResourceDictionary x:Class="MyNamespace.MyClass"
xmlns=...>
<ControlTemplate x:Key="DefaultTemplate" TargetType="ContentControl">
<StackPanel Loaded="StackPanel_Loaded">
</StackPanel>
</ControlTemplate>
C# code :
namespace MyNamespace
{
public partial class MyClass : ResourceDictionary
{
void StackPanel_Loaded(object sender, RoutedEventArgs e)
{
...
}
}
}
(you might also need to change the build action of the resource dictionary to "Page", I don't remember exactly...)

How to connect a Button in a Silverlight ListItem DataTemplate, in a ResourceDictionary (Styles.xaml), with a handler?

OK, so the situation is I'm defining an ItemTemplate for a ListBox in a ResourceDictionary (Styles.xaml). The ListBoxItem Template looks something like this:
<ControlTemplate TargetType="ListBoxItem">
<Button Command="{Binding Path=DoSomeCommand}" Content="Test" />
</ControlTemplate>
Now wherever this template is used, I'd like to have this button's click bind to an available ViewModel command to handle it.
However this does not work as is, I've also tried this:
<ControlTemplate TargetType="ListBoxItem">
<Button Command="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DoSomeCommand}" Content="Test" />
</ControlTemplate>
But still no dice.
A simple example that does work is if you define the template in the control (resources) that is using it, and just use an event handler (the same handler for all generated XAML.
Any ideas or thoughts on the best way to accomplish this? I figure this must be a common scenario: the goal is just to allow the user to interact with the items in the ListBox.
Thanks!
OK I think I answered my own question :
The solution seems to be to use 'nested' ViewModels here:
In other words, rather than have my ListBox bind directly to a collection of DTOs/business objects (as I was doing above) I instead created a simple ViewModel to wrap each DTO, and have the command on it, rather than on the original, top-level VM.
So the bound collection now looks like this:
TestItems = new ObservableCollection<ItemVM> ()
{
new ItemVM(),
new ItemVM(),
new ItemVM()
};
And each ItemVM just wraps the DTO, and has the command:
public class ItemVM : INotifyPropertyChanged
{
public ItemVM ()
{
this.MyCommand = new DelegateCommand<string> ( TheCommand );
}
public ICommand MyCommand { get; private set; }
public MyBusinessObject BizObj;
}
And voila, no need for a RelativeSource, and we have a reusable template complete with commands.
Long answer: Reference to a TextBox inside a DataTemplate
Short answer: Use Prism Commands or Blend Behaviours.

Error when binding a Dependency Property on a custom Behavior

I am exploring the Silverlight attached behaviors mechanism in order to use the Model-View-ViewModel pattern within my Silverlight applications. To start with, I am trying to get a simple Hello World working, but I am completely stuck in an error for which I'm not able to find a solution.
What I have right now is a page that just contains a button which should display a message when clicked. The click event is handled by using a class derived from Behavior, and the message is specified as a dependency property of the behavior itself. The problem comes when trying to bind the message property to a property on a viewmodel class used as the data context: I get an exeption in the call to InitializeComponent in the view.
Here is all the code I'm using, as you can see it is rather simple. First the markup of the main page and the view it contains:
MyPage
<UserControl x:Class="MyExample.MyPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyExample"
>
<Grid x:Name="LayoutRoot">
<local:MyView/>
</Grid>
</UserControl>
MyView (the TextBlock is there just to check that the binding syntax is correct)
<UserControl x:Class="MyExample.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyExample"
Width="400" Height="300">
<StackPanel Orientation="Vertical" x:Name="LayoutRoot" Background="White">
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
</StackPanel.Resources>
<TextBlock Text="This button will display the following message:"/>
<TextBlock Text="{Binding MyMessage, Source={StaticResource MyResource}}" FontStyle="Italic"/>
<Button x:Name="MyButton" Content="Click me!">
<i:Interaction.Behaviors>
<local:MyBehavior Message="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</i:Interaction.Behaviors>
</Button>
</StackPanel>
</UserControl>
Now the code, there are two classes: one for the behavior and another one for the viewmodel:
MyViewmodel
public class MyViewmodel
{
public string MyMessage
{
get { return "Hello, world!"; }
}
}
MyBehavior
public class MyBehavior : Behavior<Button>
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(MyBehavior),
new PropertyMetadata("(no message)"));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Click -= new RoutedEventHandler(AssociatedObject_Click);
}
void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Message);
}
}
Simple enough, but this code throws an AG_E_PARSER_BAD_PROPERTY_VALUE [Line: 15 Position: 43] (right at the start of the value being set for the Message property) exception when ran. I'm sure that I'm missing something, but what?
Additional information: if I remove the binding from the Message property on MyBehavior (that is, if I set its value to any static string), it works fine. Also, I'm targeting silverlight 3 RTW.
Thanks a lot!
UPDATE
It seems that unlike WPF, Silverlight does not support data binding on any object deriving from DependencyObject, but only on objects deriving from FrameworkElement. This is no the case for Behavior, hence binding does not work.
I have found a workaround here, in the form of something named surrogate binders. Basically you specify the element and property to be binded, as well as the value, as attributes of the FrameworkElement containing the non-FrameworkElement object.
UPDATE 2
The surrogate binder does not work when the FrameworkElement contains an Interaction.Behaviors sub-element.
I have found another solution here, and this one seems to work. This time, the trick used is a DeepSetter. You define one of such setters as a static resource on the containing StackPanel, and then reference the resource from the behavior. So in my example, we should expand the StackPanel resources section as follows:
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
<local:DeepSetter
x:Key="MyBehaviorSetter"
TargetProperty="Message"
BindingExport="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</StackPanel.Resources>
...and modify the button's behavior declaration as follows:
<local:MyBehavior local:DeepSetter.BindingImport="{StaticResource MyBehaviorSetter}"/>
UPDATE 3
Good news: data binding for any DependecyObject will be available on Silverlight 4: http://timheuer.com/blog/archive/2009/11/18/whats-new-in-silverlight-4-complete-guide-new-features.aspx#dobind
To get the DataBinding support the class should inherit from FrameworkElement.Hoping MSFT will give support in Silverlight 4

Resources