WPF Custom Control - ItemsControl template not being applied - c#

I'm building a custom WPF control that derives from TabControl. In the ControlTemplate, I'm using a ItemsControl to display a list that is being bound from the template (an observable collection of type FileMenuItem). During program execution, I'm getting the following error in the output window:
ItemTemplate and ItemTemplateSelector
are ignored for items already of the
ItemsControl's container type;
Type='FileMenuItem'
The type FileMenuItem is derived from MenuItem. If I change the base class to DependencyObject, the code actually runs and the template is applied (so that's an option). I googled the error and couldn't find anything about it, has anyone run into this while developing custom controls? Even though I have a workaround, I'd like to understand what's happening, and I think using the MenuItem as a base class is a cleaner implementation.
I can post more code if it would help. Thanks!

The purpose of a DataTemplate (like ItemTemplate) is to provide a visualization for a data object. Specifically, it defines a set of elements to add to the visual tree in place of the data given to an ContentPresenter or ItemsPresenter. In your case your source list is a collection of objects that are already able to be added directly to the visual tree for display in the UI.
You can see this in the following simplified example where only "Three" shows up in Red because the first two items are defined in a form that can be displayed directly by ComboBox.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Foreground="Red"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBoxItem>One</ComboBoxItem>
<ComboBoxItem>Two</ComboBoxItem>
<sys:String>Three</sys:String>
</ComboBox>

Related

How does a WPF window know how to access a nested element?

I have the following working XAML code:
<Window x:Class="DrawShape.Window1"
...
<Grid>
<Polygon Name="poly"/>
</Grid>
</Window>
In the corresponding C# code, a static callback method (for a property called Sides) accesses the poly element as follows:
static void OnSidesChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
Window1 win = obj as Window1;
win.poly.Points.Clear();
...
How is it that poly is accessed directly through Window1 win? poly is nested within a Grid element (albeit nameless). Is this type of access a feature of WPF?
PS: I am aware about the need for access through an object (because the method is static), it is the nesting that I don't understand.
You are confusing the WPF logical tree with how names are handled in XAML. In the logical tree the Polygon is contained in the Grid. However, all names belong to the same scope and are available as fields in the class generated from the XAML.
However, WPF has the concept of Namescopes which makes it possible to use the same name in multiple scopes.
Styles and templates in WPF provide the ability to reuse and reapply content in a straightforward way. However, styles and templates might also include elements with XAML names defined at the template level. That same template might be used multiple times in a page. For this reason, styles and templates both define their own XAML namescopes, independent of whatever location in an object tree where the style or template is applied.
In the simple XAML below you have a Grid named grid containing a ListBox named listBox. In the class generated from the XAML there are fields named grid and listBox allowing the code behind to access both controls.
Each list box item generated by the ItemTemplate contains a TextBlock named textBlock. However, each list box item is in a separate Namescope and there is no field named textBlock in the class generated from the XAML.
<Grid x:Name="grid">
<ListBox x:Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="textBlock" Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
In this simple example there is no need to name the TextBlock objects. However, in more advanced scenarios you may want to refer to named elements within the template, e.g. in triggers.
Locate the file Window1.g.cs in your project directory.
Window1.g.cs contains a partial class that was generated from your XAML. In there you find the variable definition for poly.

WPF set DataTemplate on Viewmodel class programmatically

I have a listbox in WPF that will contain a list of ResultsViewModel items, however the actual runtime type of these objects could be
CalculateResultsViewModel,
ScenarioResultsViewModel,
GraphResultsviewModel etc etc,
all of which extend the base abstract class ResultsViewModel.
Each of these view models should be rendered differently in the ListBox so needs a different DataTemplate. I can do that just with XAML easy enough. The difficulty is that when the viewmodels are either "processing" or when they have failed", I need them to display a DataTemplate for "processing" or "errored" which I can only so far do with Triggers. That however then means I can't use the DataTemplateSelector or a basic XAML style.
The only solution I can think of (not clean I know) is to set the DataTemplate programmatically in the SetResult() method of each viewmodel class, which is what gets called when the processing completes either successfully or with an error. In that DependencyProperty I can look at the return code and then programatically set the DataTemplate depending on the sucess/failure result. The only problem is I cannot figure out how to
Obtain a DataTemplate resource from a ResourceDictionary just using c# code - bearing in mind Im calling all of this from the viewmodel class, not the window code-behind .xaml.cs file so it doesn't have access to the properties of Window
having only a handle to the viewmodel class, somehow obtain a reference to the ListBoxItem that contains it and then programmatically set the DataTemplate on this container.
Can anyone point me in the right direction?
you can take the magic with implicit datatemplates
<ListBox ItemSource={Binding YourResults}>
<ListBox.Resources>
<DataTemplate DataType={x:Type CalculateResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type ScenarioResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type GraphResultsviewModel }>
<Grid></Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
for "processing" or "errored" viewmodels you can specify a adorner overlay in all yout datatemplates (ok but you must use the triggers)
hope this helps

DataContext in WPF

I am trying the following in my WPF application:
Structure of XAML elements.
DataTemplate[Data Type 'A']
Grid
Broder
TextBlock
I want to bind the text block's text property to a "string" which is derived from my "Users" class which is referenced in the resource dictionary of the XAML.
So in the above structure since the DataTemplate gets the feed from data type 'A'.
I want to assign the datacontext(Users) to the grid and bind the string to the textblock.
Is there a way i can achieve this ,since all my trials which include assigning the datacontext to the Grid or Border or TextBlock doesn't work.
Can any one suggest me or correct me if my approach is wrong here ?
This markup should suffice:
<DataTemplate DataType="{x:Type local:A}">
<Grid DataContext="{Binding Path=Users}">
<Border>
<TextBlock Text="{Binding Path=PropertyOnUsers}"/>
</Border>
</Grid>
</DataTemplate>
Make sure you have the namespace declared at the top of your Xaml. For whatever reason, WPF doesn't always automatically infer the template from the type if you don't use {x:Type ...}.
From there it should be straight forward.
If Users is a collection, you will have to drill into the collection to get a specific instance of User.
By the way, if you are using Visual Studio, you can use the Output window to debug binding issues.

Data Template are only necessary in ItemsControl controls?

Let me explain you my situation.
I have a base class called Shape, and several concrete classes like Triangle, Square, etc.
I have several data templates.
I'm building just one object. So I wouldn't use an ItemControl control, I would like to use a normal panel like the grid, and show the respective data template (in DataContext has the concrete item)..
The only way to do this is using an ItemsControl? Or there's another way.. because I'm just using one item and not a collection and display the correct template.
DataTemplates are used in much more than just ItemsControls
They are used to tell WPF how to draw any object in the Visual Tree. For example, if you stick a User class object in the VisualTree, a DataTemplate can be used to tell WPF how to draw that User object
They are most frequently used in controls with an ItemsSource or Content properties, because those are the most common way of inserting data objects into the VisualTree.
In your specific case where you only want to insert one data item into the VisualTree, I would suggest a ContentControl
<ContentControl Content="{Binding MyDataObject}" />
To tell WPF how to draw MyDataObject you can either use the ContentTemplate property and set it to a DataTemplate
<ContentControl Content="{Binding MyDataObject}"
ContentTemplate="{StaticResource MyDataTemplate}" />
or define an implicit DataTemplate that tells WPF to draw any object of a specific type using a specific template.
<DataTemplate DataType="{x:Type local:MyDataObject}">
<!-- Tell WPF how to draw MyDataObject here -->
</DataTemplate>
If you want to display a single item with a data template that is selected based on the item's type, you should use ContentControl or any of its derived classes.

how to create binding in code when the datatemplate is declared in xaml

i have a datatemplate declared in xaml.
for e.g.
<DataTemplate x:Key="TestTemplate">
<StackPanel>
<TextBox Name="txtBox" Visibility="Visible"></TextBox>
</StackPanel>
</DataTemplate>
I wish to set the binding for txtBox in code behind before the element is generated because i have different binding paths for different elements that get generated
I can get the template in the code behind as :
DataTemplate tmplt = FindResource("TestTemplate") as DataTemplate;
but i am not sure what to do next. How to get the the txtBox reference to set the binding.
We have to remember one thing that Templates are not instantiated UI controls. They are streamed obejcts in XAML and are shared between UI elements. So if you edit a dataTemplate and change its stucture (by adding, editing, deleting an element under the template) it would change the one data template which is shared among controls. Thus other elements using that template will also be affected by the change.
Now lets address your issue of adding a dynamic biding to a textbox. You say each generated textbox will have different binding paths. So this definitely does NOT call for changing the data template itself!
You will have to access the text box and add dynamic bindings to it AFTER the textbox's is generated.
I see that your binding differs based on your "situation", so why cant you use TemplateSelector? Template selector will decide which data template (having one specific binding applied to the TetxBox) at runtime.
The first part of answer - is FindName() method.
example:
DataTemplate tmplt = FindResource("TestTemplate") as DataTemplate;
TextBox my = (TextBox)tmplt.FindName("txtBox");
try out this, it should help to get access to TextBox control. I think that you know how to bind to. If you want your DataBinding behave different way, use MultiBinding and Converter.
EDIT
public class GeneralObject
{
private object someObject;
public GeneralObject(object initObject)
{
this.someObject = initObject;
}
//If you want to bind to some text, for example
public string Text
{
get
{
//I think you know which objects are coming as input
if (this.someObject is SpecialClass1)
return ((SpecialClass1)this.someObject).SpecialClass1TextProperty;
if (this.someObject is SpecialClass2)
return ((SpecialClass2)this.someObject).SpecialClass2TextProperty;
//and so on.
}
}
}
EDIT 2
One more possible way
So I remember, that WPF have ContentControl!
<ContentControl Content="{Binding Path=CurrentObject}"/>
But in this case you have to create number of DataTemplate's, every Template for one class.
<DataTemplate DataType="{x:Type local:SpecialClass1}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type local:SpecialClass2}">
...
</DataTemplate>
<!--and so on-->
WPF resolve DataTypes of ContentControl.Content property, and put to the ContentControl right DataTemplate.

Categories

Resources