Resources and ContentControl templates - c#

When setting a contentcontrols template to xaml in code behind I cant access a static resource contained in the parent xaml.
I have a contentcontrol as follows:
<ContentControl x:Name="ccMaterial">
<ContentControl.Resources>
<x:Array x:Key="BondListKey" Type="sys:Int32"
xmlns:sys="clr-namespace:System;assembly=mscorlib" />
</ContentControl.Resources>
</ContentControl>
then in codebehind I am setting the template as follows:
string template = "<ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
"<ComboBox Grid.Column=\"1\" Grid.Row=\"0\" ItemsSource=\"{Binding Source={StaticResource BondListKey}}\" />" +
"</ControlTemplate>";
ccMaterial.Template = (ControlTemplate)XamlReader.Parse(template);
The problem is that when i try to run this I get the exception saying that the resource "BondListKey" cannot be found. Can anyone explain why?
Please let me know if you need anymore information.
In response to Johns comments :
I have a tab item and I want to be able to display different controls within that tab based on a user selection somewhere else on the form. As an example if the user selected a car I would like to be able to change the control template to include a textbox for engine size, fuel type etc, if the user selected an orange I would like a control template that included variety and sweetness. I suspect I could get this functionality by drawing all possible controls on the tab, then altering the visible/enabled state of the relvant controls based on a datatrigger, but this would potentially involve a LOT of filtered controls ( as there may be many user selection types ). What I ideally want to be able to do is have the desired control template supplied as a string, parsed and assigned to the template of the control, thus modifying its contents at runtime.
Please let me know if that didnt make sense or you need anythign clarifying :)

StaticResource is a static lookup that is executed once at load time. If the target resource is not found at that time, you get an error, which is you're seeing now. Because you're loading the template in the context of the XamlReader the resources in your XAML aren't available. In most cases the fix is to use DynamicResource instead to provide a default value that gets updated when the resource becomes available, but Binding Source is not a DependencyProperty and so can't use Dynamic.
Rather than using a XamlReader, you can just declare your XAML in XAML and take advantage of the context that's available there:
<ContentControl x:Name="ccMaterial">
<ContentControl.Resources>
<x:Array x:Key="BondListKey" Type="sys:Int32"
xmlns:sys="clr-namespace:System;assembly=mscorlib" />
<ControlTemplate x:Key="MyTemplate">
<ComboBox Grid.Column="1" Grid.Row="0" ItemsSource="{Binding Source={StaticResource BondListKey}}" />
</ControlTemplate>
</ContentControl.Resources>
</ContentControl>
You can then still do the loading from code with:
ccMaterial.Template = ccMaterial.FindResource("MyTemplate") as ControlTemplate;

Related

Cant figure out how to set up up a static resource

I have a grid that I am trying to hook up a visibility converter to. I have a class set up in the HelperObjects namespace, but for some reason the xaml is not picking up the reference. The error message is "The resource BoolToVisConverter could not be resolved" Why won't the xaml pull in this resource? I am sure I am doing something ridiculous here...
Here is the xaml:
xmlns:HelperObjects="clr-namespace:foo.HelperObjects"
...
<Grid Visibility="{Binding isZoneTwoVisible, Converter={StaticResource BoolToVisConverter}}">
The error message is indicating that the resource identified by the key BoolToVisConverter, to which you're trying to bind, cannot be found.
The most likely explanation is that you haven't declared that resource within a scope that can be accessed by your xaml. You'll want to create a StaticResource with a key matching the name you're referencing, within a Resources section of your xaml, the exact location may vary depending on your needs/structure.
Assuming you're doing this within a Window, you could do something like:
<Window>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</Window.Resources>
<Grid Visibility="{Binding isZoneTwoVisible, Converter={StaticResource BoolToVis}}">
<--...Content...-->
</Grid>
</Window>
Note: I haven't included your namespace in front of the BooleanToVisibilityConverter because this is a class which already exists within the framework.
It may be the case you require slightly different behaviour, or don't have access to that class, in which case you may need to add your namespace when defining the resource, e.g. <HelperObjects:BooleanToVisibilityConverter x:Key="BoolToVis"/>
Potentially useful further info about static resources: https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/staticresource-markup-extension
Try to specify the converter in UserControlResources or even parent level Grid Resources. Assuming you are using a UserControl.
And your code is unclear as of where you are defining the StaticResource BoolToVisConverter. But generally heres the process.
Look at this sample code below to define your StaticResorce in the UserControl Resources section. Make sure you set the DataContext where isZoneTwoVisible is residing. I am hoping you implemented ImplementINotifyPropertyChanged and said PropertyChanged on your isZoneTwoVisible or isZoneTwoVisible is a DependencyProperty. Note: BooleanToVisibilityConverter is a class that implements an IValueConverter or if your property is a bool you don't even need that class.
<UserControl.Resources>
<BooleanToVisibilityConverter
x:Key="boolToVisibility"></BooleanToVisibilityConverter>
</UserControl.Resources>
You did all the things said above and it still dosen't work, sometimes I specify the relative Source hoping its residing in a UserControl.
Visibility="{Binding isZoneTwoVisible,Converter={StaticResource boolToVisibility},RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}}">

Binding works fine, but intellisense says: Cannot resolve property XXX in data context of type 'object'

I have a binding to a parent-element. How can I provide the data type for the DataContext in the binding, so intellisense can resolve the bound Properties?
The binding works fine at runtime. So, I have the following XAML structure:
<TabControl Name="TabDynamic"
ItemsSource="{Binding TabItems, Mode=OneWay}" ...>
<TabControl.Resources>
<DataTemplate x:Key="TabHeader" DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Header}" />
<Button Command="{Binding ElementName=TabDynamic, Path=DataContext.DeleteTabCommand}"
CommandParameter="{Binding ElementName=TabDynamic, Path=DataContext.TabItems/}">
<Image Source="{DynamicResource DeleteImg}" Height="11" Width="11"></Image>
</Button>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="viewModels:ConnectionInfoVM">
<views:BufferViewerControl/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
The data type of the DataContext is "viewModels:ConnectionInfoVM".
Intellisense will now underline both Properties on the DataContext (so DeleteTabCommand and TabItems are underlined).
I already tried to use the design-time data-context definition "d:DataContext" within the Button element like so:
d:DataContext="{d:DesignInstance viewModels:ConnectionInfosVM}"
But this does not change the intellisense warnings.
I also tried to define the DataType on the DataTemplate to be "viewModels:ConnectionInfosVM", as I do for the content-template, but that too does not change the intellisense warnings (and I guess would be wrong, as the data type of the element really is a TabItem).
Another try was to define the DataContext by adding the following to the Button element definition:
<Button.DataContext>
<viewModels:ConnectionInfosVM/>
</Button.DataContext>
But this too, does not get rid of the warnings.
I found a solution at least for the above described problem. I was setting the DataContext of the window in question in the code-behind. The TabControl in my example just inherits that DataContext, which is a ConnectionInfosVM.
In order to fix those warnings (and of course for the gained flexibility in providing a DataContext by a Locator), I defined the DataContext in XAML like so:
<UserControl ...
DataContext="{Binding Source={StaticResource mainViewModelLocator}, Path=ConnectionInfosVM}">
It seems that intellisense is now able to resolve those references. But on the other side, I also changed the TextBlock contained within the DataTemplate to the following:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=DataContext.Address}" />
Now the DataContext of such a TabItem is actually a different type, as it is the type of the backing object for the TabItem. So in this case, the Property Address cannot be found by intellisense.
So, the question kind of remains, is there a possibility to define the type of the DataContext within a binding defined in a DataTemplate?
I needed to add the mc:Ignorable="d" attribute to the Window tag. Essentially I learned something new. The d: namespace prefix that Expression Blend/Visual Studio designer acknowledges is actually ignored/"commented out" by the real compiler/xaml parser!
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
The following was taken from
Nathan, Adam (2010-06-04). WPF 4 Unleashed (Kindle Locations 1799-1811). Sams. Kindle Edition.
Markup Compatibility
The markup compatibility XML namespace (http://schemas.openxmlformats.org/markup-compatibility/2006, typically used with an mc prefix) contains an Ignorable attribute that instructs XAML processors to ignore all elements/attributes in specified namespaces if they can’t be resolved to their .NET types/members. (The namespace also has a ProcessContent attribute that overrides Ignorable for specific types inside the ignored namespaces.)
Expression Blend takes advantage of this feature to do things like add design-time properties to XAML content that can be ignored at runtime.
mc:Ignorable can be given a space-delimited list of namespaces, and mc:ProcessContent can be given a space-delimited list of elements. When XamlXmlReader encounters ignorable content that can’t be resolved, it doesn’t report any nodes for it. If the ignorable content can be resolved, it will be reported normally. So consumers don’t need to do anything special to handle markup compatibility correctly.

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.

Josh Smith MVVM Demo app

In MainWindow we have:
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Workspaces"
Style="{StaticResource MainHCCStyle}"
/>
In the resources:
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
And in the article says:
A typed DataTemplate does not have an x:Key value assigned to it, but
it does have its DataType property set to an instance of the Type
class. If WPF tries to render one of your ViewModel objects, it will
check to see if the resource system has a typed DataTemplate in scope
whose DataType is the same as (or a base class of) the type of your
ViewModel object. If it finds one, it uses that template to render the
ViewModel object referenced by the tab item's Content property.
My question is:
How does the template know that the type is a collection of workspaces (WorkspaceViewModel)?
It doesn't need to, in the code you've posted. In your sample, you have given a strict value to your content template: you've explicitly used {StaticResource WorkspacesTemplate}, and so a resource with the key of "WorkspacesTemplate is looked up.
Because you've explicitly set the template, it doesn't matter what the intended type is: it'll try to display any object in your Content using the template you've set - with varying degrees of success if you use a type that doesn't match!
In the alternate method you mention - with a "typed DataTemplate", you would declare your datatemplate with <DataTemplate DataType="{x:Type l:WorkSpace}" />. Note that there is no x:Key (and also that I've assumed you have a namespace l mapped to your local code). What happens here is that WPF automatically sets the key of your resource to the DataType (important to note: a resource key doesn't have to be a string!).
Then, when you declare your HeaderedContentControl, you can leave out setting the ContentTemplate. At runtime, when the control is rendered, WPF will check the type of the Content object and find that it is WorkSpace, and it'll then look up a resource with x:Key="{x:Type l:WorkSpace}" - which will match your typed template.
This is a useful way of making consistent representations of data throughout your application, since a typed DataTemplate will be used automatically by any content-presenting control throughout your application.
WPF doesn't really care about the concrete type, it's just need to be some IEnumerable of something, WPF uses the type descriptor to know what the ui binding with.

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.

Categories

Resources