UWP, ContentControl DataTemplate creates multiple instances of the child control - xaml

Here is the context:
I have a list view with a list of files, which can be different type.
I want to display different content when a different type of file is selected.
<ContentControl x:Name="ContentContainer" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
DataContext="{x:Bind SelectedFile, Mode=OneWay}"
ContentTemplateSelector="{StaticResource TemplateSelector}" >
<ContentControl.Resources>
<DataTemplate x:Key="GenericFileTemplate">
<mytemplates:GenericFileTemplate SelectedFilter="{Binding ElementName=Host, Path=SelectedFile}" />
</DataTemplate>
<DataTemplate x:Key="PhotoFileTemplate">
<mytemplates:PhotoFileTemplate SelectedFilter="{Binding ElementName=Host, Path=SelectedFile}"/>
</DataTemplate>
<DataTemplate x:Key="VideoFileTemplate">
<mytemplates:VideoFileTemplate SelectedFilter="{Binding ElementName=Host, Path=SelectedFile}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
Host is basically just the page itself, which has a dependency property of SelectedFile.
I have tried different ways to explicitly trigger the ContentControl update, including
Directly find resource in the code behind, such as
ContentContainer.ContentTemplate = FindTemplate(SelectedFile);
Using a TemplateSelector, but I have to recreate the ContentTemplateSelector each time a different file is selected
ContentContainer.ContentTemplateSelector = new FileTemplateSelector();
Both cases work.
However, one thing I notice, if I switch file type back and forth from different types. Multiple child control will be created. For example, the sequence:
select a generic file first,
the generic file template is selected,
the generic child control is created;
select a photo file
the photo file template is selected
the generic child control still respond, because the host's SelectedFile is changed, but I can ignore it
the photo child control is created
select a different generic file now
the generic file template is selected.
the first generic file child control responds (sure, I can accept it)
the photo child control responds
now a new generic file child control is created (this seems to be wrong)
so if I keep switching, the correct datatemplate is selected and assigned to my ContentControl, and then a new child control is created...
Is there a way I can make it to reuse the DataTemplate and its children if it's already loaded?

From what I can tell, no there is no way you can force the ContentControl to reuse the old DataTemplate, especially if you keep swapping out templates or templateselectors yourself instead of changing the content. This is partially because ContentControl can't know how many templates there are, so if it were to cache it, you could end up with a lot of memory consumption.
Even if you cache the datatemplate itsself, you can not cache the rendered child control because of how ContentControl uses the DataTemplate. Other controls such as ItemsRepeater however cache the actual rendered children (also called "container").

Related

.NET MAUI Communitytoolkit.Mvvm: How to bind an object instead of a string

i already the project from the official tutorial of .NET MAUI until step 5 using Communitytoolkit.Mvvm:
text
Now, instead of binding only a Text (which is a standard type that can be accessed from everywhere) i would like to bind a simple object (called ItemGroup) with two members (bool isChecked and string name).
How to do that?
For a global access i made this class in the MainView folder called ItemGroup. This class is not accessable and i don't know how to do that it is.
I changed the code in the MainPage.xaml like this:
<CollectionView ItemsSource="{Binding Items}"
Grid.Row="1">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="{x:Type x:ViewModel.ItemGroup}">
<Grid >
<CheckBox IsChecked="{Binding ItemGroup.IsChecked}" Grid.Column="0"/>
<Label Text="{Binding ItemGroup.name}" Padding="10" Grid.Column="1"
BackgroundColor="LightGray"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
See also the project structure with the ItemGroup class in the ViewModel folder as well as the error message:
where the content page is declared as this:
Remark: The MainViewModel looks like this:
Should i declare some uses, or namespace?
where in the project should i place the Class of the objects i would bind?
Thanks in advance, Thomas
Also tryed to implement the class ItemGroup in the MainViewModel.cs but then i had any more problems with access to this class.
Try this
<Label Text="{Binding name}" …
Note that name must be a public property
You have a number of issues here.
The community mvvm toolkit includes code generators.
A variable:
[ObservableProperty]
string text;
Is examined by the code generator at compile time and in a partial class it will add a public property Text.
Binding is case sensitive and you need to bind to public properties so you need to use upper case on that first letter. Binding Text rather than Binding text.
You might have other instances where you did the same sort of thing.
I don't see where you use it but that add [Relaycommand] will generate AddCommand as a public property which you bind to.
Additionally you have misunderstood how an itemscontrol gets an item out a bound collection per row. As DevenCC points out, once you bind ItemsSource to Items then each row will get an instance of whatever is in that collection so you should bind IsChecked rather than ParentProperty.IsChecked.
As to where to put classes. In large apps it is a nuisance to flip between a folder contains views and another contains viewmodels. You might want to consider a folder contains each pair of view and viewmodel. Hence a foo folder containing fooView and fooViewModel
I believe your problem lies with you Binding declaration inside you ItemTemplate. Given that you bound your CollectionView's ItemSource to "items", the ItemTemplate's data context is now a single itemGroup from your list. Therefore, you should not be writing
<CheckBox IsChecked="{Binding ItemGroup.IsChecked}"[...]
But instead
<CheckBox IsChecked="{Binding isChecked}"[...]
Since you are already "within" a single itemgroup object. Same goes for your Label. Furthermore, I don't think you actually need to declare the Datatype for your item's DataTemplate.
p.s. Watch out for your item declaration, it seems like you have both items and Items declared in your ViewModel; and both seem public.

What's a comprehensive list of parts of an ItemsControl?

The UWP XAML ItemsControl is the basis for many complicated XAML classes, like ListView and GridView.
The documentation Item containers and templates describes 2 key parts of these controls:
Data template
Control template
These parts combine to create the final view:
Container controls (such as ListViewItem and GridViewItem) consist of two important parts that combine to create the final visuals shown for an item: the data template and the control template.
In practice, developers specify the data template by specifying a DataTemplate in ListView.ItemTemplate (or GridView.), and they can customize the control template by providing a Style (TargetType="ListViewItem") to ListView.ItemContainerStyle.
Developers can also customize the ListView.ItemsPanel (which is an ItemsStackPanel by default), and the default Template for the ListView.ItemContainerStyle contains a ListViewItemPresenter. The documentation for Item containers and templates mentions these, too.
That raises the question:
When I add a ListView (or GridView or any ItemsControl) to my code, what am I actually adding? What can I customize? How is my data displayed?
As far as I can determine, the ListView looks something like:
ListView
Renders its Template which somehow renders:
ItemsPanel
Renders its ItemsPanelTemplate which is:
ItemsStackPanel
Renders, for each item:
ListViewItem
Renders its Template, which is:
ListViewItemPresenter
Somehow renders:
ListView.ItemTemplate
But this is unclear to me.
Disclaimer: I work for Microsoft.
you can customize anything in xaml
under the hood, both ListView and GridView can be created by ItemsControl, but their default template have some customization built-in already.
if you want to understand when to use which, here is a page:
https://learn.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/lists
your data will be set to the ItemsSource if using binding, for example
<ListView
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
>...
or you can set directly.
from your question, it sounds like you may not have understood the basis, so maybe reading this series will help you:
http://drwpf.com/blog/itemscontrol-a-to-z/
it's for wpf, but the fundamental is the same, you can apply it to UWP as well.

UWP MVVM XAML Dynamic UserControl Manager

I need help with a change of perspective.
I got stuck trying to approach UWP in a way I used to do in WPF regarding a MVVM pattern for managing UserControls dynamically.
I naturally tried to perform the same pattern in UWP but got stuck on various things like UWP not supporting 'x:Type' ...
Situation is; time to rethink this approach and look for a new direction. Seems I'm forced to abandon to use implicit binding in a similar fashion to the WPF pattern, using the Content property of a ContentPresenter and a VM property 'of type Object', which maintain a selected ViewModel. It was a simple and clean approach for matching up the correct View automagically with the VM set in ActiveViewModel.
the below was such a simple way of managing many views all over the place, odd MS not fixing this? But, back to the big Q: what now in UWP!?
<ContentPresenter Content="{Binding ActiveViewModel}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:OneViewModel}">
<local:OneView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:TwoViewModel}">
<local:TwoView />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
What Shall I do instead of this!? Anyone found a new efficient way of doing it? I got stuck in my stubborn mind and need someone to kick my butt so I go forward. Getting to old to change, but due to this profession it seems I constantly have to. :)
Looking at the DataTemplate documentation, there's a paragraph explaining the situation which you are trying to figure out.
For advanced data binding scenarios, you might want to have properties
of the data determine which template should produce their UI
representations. For this scenario, you can use a DataTemplateSelector
and set properties such as ItemTemplateSelector to assign it to a data
view. A DataTemplateSelector is a logic class you write yourself,
which has a method that returns exactly one DataTemplate to the
binding engine based on your own logic interacting with your data. For
more info, see Data binding in depth.
Here, you have an example on how you can select distinct DataTemplate for items in a control such as a ListView based on defined conditions.
Your situation is a bit different from the one described above, but the solution should be within what is explained above.
Create a class which derives from DataTemplateSelector, and override the SelectTemplateCore methods exposed by it, where you define the logic of what DataTemplate should be selected for the specific presented object.
This Derived class should expose properties of type DataTemplate, which identify each single DataTemplate template object, you pretend to be able to choose from.
Just as in the example, you are probably better of by defining the DataTemplate resources on an higher level object, such as the Page itself.
Instantiate your DataTemplateSelector Derived class in XAML as a resource and set each of the properties exposed above of type DataTemplate to the analogous DataTemplate static resource.
Utilize the ContentTemplateSelector dependency property, by setting it your custom DataTemplateSelector.
With this logic, it should be possible to have your ContentPresenter decide correctly between which DataTemplate it should choose from, based on your required UI logic.

Alternative to ElementName in x:Bind with DataTemplates

When using traditional {Binding} syntax you could specify element name to point to a specific control on the page, and be able to access its properties. For example if the page is named page you could do:
{Binding ElementName=Page, Path=Name}
With the {x:Bind} syntax it says
With x:Bind, you do not need to use ElementName=xxx as part of the
binding expression. With x:Bind, you can use the name of the element
as the first part of the path for the binding because named elements
become fields within the page or user control that represents the root
binding source.
So for the example above in {x:Bind} would be
{x:Bind page.Name}
Which works fine, until it is inside a data template (for example a ListView's ItemTemplate). In which case it no longer works as it is looking for Page on the data type specified which leads to the following error (assuming my data type is customer):
XamlCompiler error WMC1110: Invalid binding path 'Page.Name' :
Property 'Page' can't be found on type 'Customer'
What is the solution to use {x:Bind} syntax with datatemplates and access controls outside the data template?
Example code is available here (note specific commit)
As far as I know at this point in time there is no way to directly bind to a property of a control using the x:bind method as it does not support the element name inside of its binding definition.
That does not mean you cant bind to a control inside a dataTemplate you can still do something like this to access controls but you just aren't able to use the compiled binding x:Bind syntax.
<DataTemplate x:DataType="local:Customer">
<StackPanel Orientation="Vertical">
<Button Content="{Binding Name, ElementName=page}" />
<TextBlock Text="{x:Bind Title}" />
</StackPanel>
</DataTemplate>
The reason for the error you are getting is due to the way data templates parent their datasource. The x:Bind binding cannot reference a control object and your Customer type does Page.Name property or path. As shown above the only real way of accessing user control properties outside of your control only using XAML is to resort back to the standard binding mechanism.
I hope this answers your question.

What's the DataContext of the Header elements inside a Hub control?

I have started to develop for WP 8.1 using Windows Runtime recently, and I have faced a... "problem" that I don't seem to understand.
The application I am currently developing uses a Hub control, and I would like to tweak its header's appearance slightly. To do so, I changed the HeaderTemplateattribute of the Hub control. However, as I tried to localize the textual content of the header (note that this is WinRT, the localization process is slightly different from the process in WP8 and can be found here), I "accidentally" fixed the problem by making the template like this:
<Hub.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Foreground="Red"/>
</DataTemplate>
</Hub.HeaderTemplate>
I don't understand why it worked, though. When you do data binding like this (just using the expression {Binding}), doesn't the element get the same DataContext as its father? What's happening under the hood? Who's the parent element of the TextBlock, after all?
Thanks in advance.
EDIT
As igrali asked, here is a more complete view of the XAML:
<Page ...
DataContext="{Binding Data, RelativeSource={RelativeSource Self}}">
<Grid Background="#FFF6DB">
<Hub Name="MainPageHub"
x:Uid="MainPageHub"
Margin="0,27,0,0">
<Hub.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Foreground="White"/>
</DataTemplate>
</Hub.HeaderTemplate>
...
Then, in the /Strings/en-US/Resources.resx, I have a "MainPageHub.Header" property set to "foobar" (just an example), and what I get as a header is actually "foobar" (which is what I wanted, but even so it seems confusing!).
As Tim Heuer explains it here
Notice the x:Uid value here. This now maps back to the key in your
RESW file. Anything with that starting key will have properties
merged into it. So we can have a key in our RESW for “Text” using the
key name MyTextBlock.Text with a value of “Hello World” and the
runtime will do the replacement for you. This applies to properties
other than text (i.e. width) as well as attached properties.
All I can add is - notice the x:Uid. It's MainPageHub. In the localized resource file, you have a MainPageHub.Header. This means that the value of the resource string will be set to the Header of the control which has the x:Uid set to MainPageHub.
So, now that it's clear how the Header is set - there's still the binding part. Well, considering you have a different template for the Header, it needs to do the {Binding } part to actually get the value of the header which is set through the resources.