How do I implement a WPF control with its own DataTemplate DependencyProperty? - c#

I am an intermediate WPF developer, with working knowledge on how to implement dependency properties as well as simple custom controls. I do not yet understand how I can add a DataTemplate dependency property to a custom control, and use it to define the element tree for each datum in a collection of data.
The full story is that I have been working on creating a WPF map control that displays many different points and geometric shapes on the map, over map tiles. These shapes will translate with the rest of the map when user "drags" the map around.
I have accomplished this, insofar that I have created the map control, and can add child elements to it in Xaml that have map coordinates. I would like to take this farther, and add properties for collections of data, i.e. points, areas, etc. To better understand what I'm looking for, I would like to re-create two properties from ListBox: ItemsSource and ItemTemplate.
I have added two dependency properties to my Map control - PointsSource and PointsTemplate. PointsSource is of type IEnumberable and represents the collection of data to display on the map. PointsTemplate represents what each of those datum should look like. Simply throwing these properties into my control is obviously not enough, but I am unsure of how to coordinate them with one another. If anyone has working knowledge of creating a custom data control with it's own DataTemplate properties for changing the UI tree for each data element, I would really appreciate it.

I have found what I am looking for in the DataTemplate itself. The DataTemplate provides a function for code behind called LoadContent(). LoadContent produces a dependency object that represents the tree of content for a given datum. From what I have found elsewhere, the common use for LoadContent might look like the following:
foreach (object point in PointsSource)
{
FrameworkElement pointElement = _PointsTemplate.LoadContent() as FrameworkElement;
pointElement.DataContext = point;
this.Children.Add(pointElement);
}
The above code will add a content tree for every single element of data, and we give it the datum to bind its DataContext to.

If anyone has working knowledge of creating a custom data control with it's own DataTemplate properties for changing the UI tree for each data element, I would really appreciate it.
Basically, you will want to use an ItemsControl inside your control template, and bind its ItemsSource and ItemTemplate properties to your custom Dependency Properties. Ie,
<Style TargetType="{x:Type local:CustomControl}">
<Setter Property="Template">
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<ItemsControl ItemsSource="{TemplateBinding PointsSource}"
ItemTemplate="{TemplateBinding PointsTemplate}"
/>
</ControlTemplate>
</Setter>
</Style>
(assuming the DPs IEnumerable - "PointsSource" and DataTemplate - "PointsTemplate")

Related

xaml - How to specify which ContentTemplateSelector to use?

I've seen a ton of examples where a content control's ContentTemplateSelector property is assigned a StaticResource.
Example: <ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource MyTemplateSelector}"/>
MSDN states:
Typically, you create a DataTemplateSelector when you have more than
one DataTemplate for the same type of objects and you want to supply
your own logic to choose a DataTemplate to apply based on the
properties of each data object.
Source: DataTemplateSelector Class
Knowing this - my situation is that I have two types of objects where each object has its own set of data templates it can use. Is there any way I can simply have the content control's ContentTemplateSelector bind to a ContentTemplateSelector property on the view model? The tricky part is that the data templates are defined in the xaml file - I can't just initialized a new instance of the specific ContentTemplateSelector for that class.
Additional info: I have a parent view model where each of its subclasses should be represented as a different type of object. So depending on which subclass view model is toggled, it should use its respective ContentTemplateSelector and data templates.
I've also gotten the above to work with a converter, but I want to stay away from this. Ideally, I'd like to have code that makes this process as general as possible. i.e. Not needing to maintain the converter code to add new types of objects in the future just to return the right DataTemplateSelector. The case should be that anytime a new subclass is added, it'll just work right away.

Stop datatemplates from being inherited in nested contentpresenters

I use a range of viewmodel classes in my application to represent the data. I define 2 or more "ViewPresenter" controls (based on the ContentPresenter) to show these viewmodels in for instance "minimal", "summary" or "detail" modes. Each of the presenter classes use a resourcedictionary to define the datatemplates for all the viewmodels it needs to be able to show.
Now here's the problem: When different types of these viewpresenters are contained within each other, they inherit datatemplates from their containers, which leads to some issues. How can I stop that inheritance from happening?
Edit: Example for illustration
// first ContentPresenter
<ViewPresenter DataContext="{Binding DerivedObj}">
<ViewPresenter.Resources>
<DataTemplate TargetType="{x:Type DerivedClass}">
<DerivedClassView>
// Nested ContentPresenter
<ViewPresenter DataContext="{BaseObj}">
<ViewPresenter.Resources>
<DataTemplate TargetType="{x:Type BaseClass}"/>
</ViewPresenter.Resources>
</ViewPresenter>
</DerivedClassView>
<DataTemplate/>
</ViewPresenter.Resources>
</ViewPresenter>
So the problem that occurs here is that the nested template is less specific than the one in the higher level contentpresenter, and so it is never shown. The nested contentpresenter needs to start with a clear resourcedictionary, and refrain from using the higher contentpresenter's datatemplates.

how to render DataTemplate in UI

In .NET/WP/WPF, I am looking to create my first user control that will render content using a DataTemplate and am wondering how to do that. Do I need to use the Content presenter and pass it a reference to the template or what? Thanks for the help guys!
The basic strategy for including templated content in other fixed content (like the XAML of a UserControl) is to define a set of Content properties (as DependencyProperties) on the containing control and then add a ContentPresenter (with appropriate bindings) as the placeholder into which the content will be injected. In the framework you can see an example of this in HeaderedContentControl which has both a normal Content property set, but also a parallel set of Header properties that are used as a second piece of content.
The properties you can define on your control (differing by platform) are:
Content
ContentTemplate
ContentTemplateSelector
ContentStringFormat
with whatever your custom name is substituted for "Content" in each. In your case you probably only have the first two. Then in your UserControl layout (which is actually defining the Content itself of the UserControl) just place a ContentPresenter and set it up to use your custom properties with the control itself as the Binding Source (ElementName, RelativeSource, or setting the DataContext somewhere to the UserControl itself):
<ContentPresenter Content="{Binding ElementName=MyControl, Path=MyExtraContent}"
ContentTemplate="{Binding ElementName=MyControl, Path=MyExtraContentTemplate}" />
In most cases (but not here) ContentPresenter is used inside a ControlTemplate where you can use a nice shortcut that's built in to bind all the content properties for you:
<ContentPresenter ContentSource="MyExtraContent"/>
You can get the same effect with ContentControl but it's adding extra elements to your visual tree since it's basically just a ContentTemplate containing a ContentPresenter that passes all the properties through. It does allow you to add some visual differences, like Background or Padding, or add a whole custom template but in cases like this you can do exactly the same thing by just adding other controls around your ContentPresenter.

WPF Treeview - How to automatically expand parents and select new node

I have model objects set up like so:
public class Model
{
public ObservableCollection<Model> Children{get;set;}
public string Name{get;set;}
}
This gives me a tree of model objects of arbitrary size and depth. I display these in a WPF TreeView using a hierarchical datatemplate. Because of the ObservableCollection, I can add model objects anywhere in the model tree and the UI will update accordingly.
The annoyance comes because even though a new model object might be added at the third level, for example, it might not be immediately visible.
When a new model object is added anywhere in the tree, I would like the TreeView to automatically select the new node, and expand its parents to that it is immediately visible to the user. This doesn't seem to be immediately obvious.
What I don't want to do is pollute the Model objects with properties which only make sense in a particular WPF control. Nor do I want to add a parent property to the Models.
It seems that this must have been tackled before by someone. Does anyone have an idea on how to tackle this?
Create 2 properties in your model as below
IsExpanded and IsSelected
and in your treeview ItemContainerStyle add the below setters
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
Noe everytime you set these 2 properties in your viewmodels, the corresponding treeviewitem will be selected/expanded

How to get or set custom object as datacontext instance to custom control in wpf

I've created a custom control that is styled and configures in its own XAML sheet. Databindings in this control uses a specific object (CProject class).
Just to clarify, the control is a project frame, that has controls for settings and a canvas that will be the workspace for each/any project.
The project control (IPProjectPanel) inherits "Frame", and also adds a "settings" stack panel to its children list which in turn contains controls for - well, settings.
The CProject class however, is the pure functional part, with no UI interaction or handling whatsoever. So, I need to "plug" an instance of CProject into every unique project that can be active. So, I want to set a specific instance of CProject as datacontext to every IPProjectPanel instance in a tabpanel. Either I want to set the datacontext by code, or have it created by settings datacontext in XAML, and retrieving it after it has been initialized.
The problem though, is that I cant quite figure out either.
Here is a snippet of the style of the IPProjectPanel in XAML, that uses the approach to set datacontext in XAML:
<Style TargetType="{x:Type ip:IPProjectGrid}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ip:IPProjectGrid}">
<Grid Background="White"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="0">
<!---->
<Grid.DataContext>
<ipp:CProject></ipp:CProject>
</Grid.DataContext>
<StackPanel x:Name="PART_settingsPanel"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
MinWidth="300" Background="Gray">
<GroupBox Header="Project settings">
<StackPanel>
....
</style>
Here it is set as a context to Grid, but I'd like to have it as a context of the actual class (IPProjectPanel).
So, the IPProjectPanel instance is created by code (for now..), and I need to retrieve the CProject instance (or set one) so that I can work with it.
I'd like to keep to C#/WPF ways to do stuff, as this app is also training for WPF and C# concepts and such. So the "best C#-WPF" way to do it, is very welcome, but a solution either way!
Thank you for your time.
So in general, the datacontext is primary inteded to be for your ViewModel, and in fact WPF is really set up for doing MVVM (Model View ViewModel) style applications. It's actually fairly simple to learn, but if you're looking for the "Best C#-WPF" way of doing things, take the time to learn MVVM. It's really fairly straightforward.
Simple Example from CodeProject:
http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
From Microsoft (somewhat heavy reading):
http://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx
The way you have this set up currently has the potential to create some nasty bugs. You should never declare a DataContext object instance inside a template unless you never plan on accessing it outside of that template's scope. By doing so you will be creating a new instance of the CProject class any time the control needs to be visually re-loaded (like changing tabs) and you may end up referencing an old CProject instance in code while displaying a completely separate one on the screen. Declaring a DataContext object not in a template (i.e. Window.DataContext) is fine.
If you want each control instance to create its own CProject instance you would be better off doing that in code in the constructor and exposing that as a property on the control which you can then bind your Grid.DataContext to inside the template. Avoid setting it to the DataContext property of the control itself as this will cause any implicit source Bindings that are set on the control where it is declared in XAML to break by overriding the inherited DataContext:
Grid.DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PropertyWithCProject}"
It's probably more likely that you will want to control the CProject instances externally and hand them to the control instances. To do this you can create them in a container ViewModel class (MVVM pattern) and set this as a DataContext higher up on something that will contain all of your custom controls. You can then expose individual CProjects or a collection of them and bind your controls' DataContexts to those.

Categories