WPF: Data Template vs View - c#

What's the value of using a DataTemplate to assign a ViewModel to a View? I see lot's of code that looks like this and am doing the same myself.
ViewResources.xaml
<DataTemplate DataType="{x:Type vm:GenericViewModel}">
<vw:GenericView />
</DataTemplate>
View.xaml
<ContentControl Content="{Binding Generic}" />
What are the advantages compared to displaying the View and binding to the DataContext?
View.xaml
<vw:GenericView DataContext="{Binding Generic}" />
At a minimum this appears to require less code and also plays "nicer" with the designer. I can see the need for a DataTemplate(say you're styling a TextBlock or something simple), but once you have created a View what is the point?

Related

(UWP) How do I access my code-behind file from within a DataTemplate?

This is basically a data binding issue.
Simply put, I have a ListView who's items are generated and use a DataTemplate to present those items. Within a DataTemplate, however, the DataContext changes to the x:DataType being used, and so I have no way to access properties located in my code-behind C# file, or even properties contained in my ViewModel. Hopefully I am being clear here:
<ListView
x:Name="MyListView"
ItemsSource="{x:Bind mainViewModel.AdvancedNoteCollection, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="Models:Note">
<Grid>
<TextBlock Text="{ When trying to bind, I am stuck within the DataContext of the Note data type! }" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
However, in reality, my DataTemplate is located in a external ResourceDictionary file, which makes my situation a little more challenging. The external ResourceDictionary DOES have its own code-behind file, though, which allows me to use x:Bind in addition to traditional Binding.

UserControls not rendering

Okay, to start, I'm pretty inexperienced with WPF and XAML, so any pointers or advice would be greatly appreciated.
I have a scheduling program that I'm working on that I need some help setting up. I had things working previously, but it wasn't organized correctly. I had UI elements in my ViewModels that I would add to a StackPanel at the initialization of the MainWindow. Generally not MVVM style coding. So I made some views (UserControls) to display the things I have, and most everything broke.
Basically, I have a Schedule ViewModel that has some parameters and a list of a different Room ViewModels. Each Room ViewModel has a RoomSchedule ViewModel that contains a list of RoomEvent ViewModels.
I'm trying to write controls for the things that need displaying. I've created a Schedule view, which has a list box of Room views, and the Room view uses the RoomEvent view to display the events of the room. The Room view uses the WPF Extended Toolkit's TimelinePanel, the rest of the controls are pretty much basic controls. The general idea has been: a model provides data to the ViewModel, which massages that data to what needs to be displayed. So an Event should know how to display itself, a Room should know how to display itself, and the Schedule should know how to display itself.
The problem I'm running into is: now that I've scooted everything from the xaml.cs or ViewModel files to their appropriate places, the controls aren't rendering at all. I've been reading other SO postings where people have the same problem, but none of them seem to work for beginner stuff like this. I think I'm close, it seems like all the controls are being created, and the DataContext's are being set correctly, but nothing is showing up.
This is, basically, what I have so far. I left some of the xaml boilerplate stuff off for succinctness:
Schedule.xaml:
<StackPanel>
<ListBox ItemsSource="{Binding Rooms}" >
<ListBox.ItemTemplate>
<DataTemplate>
<localcontrols:RoomView ScheduleStart="{Binding ElementName=ScheduleControl, Path=DataContext.Start}"
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
</StackPanel>
RoomView.xaml:
<extended:TimelinePanel BeginDate="{Binding localcontrols:ScheduleStart}" EndDate="{Binding localcontrols:ScheduleEnd}"
<ItemsControl ItemsSource="{Binding Path=mRoomSchedule.mScheduledEvents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<localcontrols:EventView />
</DataTemplate>
</ItemsControls.ItemTemplate>
</ItemsControl>
</extended:TimelinePanel>
EventView.xaml:
<Border BorderThickness="1" BorderBrush="Black" extended:TimelinePanel.Date="{Binding mStartTime}" extended:TimelinePanel.DateEnd="{Binding mEndTime}">
<TextBlock Background="{Binding mColor}" Text="{Binding mEventID}" />
</Border>
The ScheduleStart and ScheduleEnd are dependency properties defined in RoomView.xaml.cs. My thinking was that Schedule would have Start and End properties that would be set in its constructor, and the RoomViews in the ListBox would bind to those properties to set the TimelinePanel's BeginDate and EndDate.
Maybe your bindings are wrong. When I need to bind to a dependency property I use the ElementName feature of binding to say which control I want and I give the root node a name, in this case Root. It's one way to solve it.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Weingartner.Controls"
x:Class="RoomView"
x:Name="Root">
<extended:TimelinePanel
BeginDate="{Binding ElementName=Root, Path=ScheduleStart}"
EndDate="{Binding ElementName=Root, Path=ScheduleEnd}"
>
<ItemsControl ItemsSource="{Binding Path=mRoomSchedule.mScheduledEvents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<localcontrols:EventView />
</DataTemplate>
</ItemsControls.ItemTemplate>
</ItemsControl>
</extended:TimelinePanel>
</UserControl>

Tab data lost on selected item changed MVVM

I have been trying to do this for ages and having no joy whatsoever.
I have a ribbon window of the following hierarchy:
MainWindow
MainContent (Tab Control)
TabContainerViewModel
ViewModelBase
View model base has an ObservableCollection of tabs type ViewModelBase. The Tab Control itself is binding fine to these, displaying appropriate view models. I have 2 problems however, I want a "NotepadView" (Of type viewmodelbase) to be replicated numerous times (one view many view models).
At the minute, I have 4 views (NotePadViewModelx where x is 1-4) with corresponding viewmodels, this is because each view had the same text per tab. Now I have individual views per tab.
When I type into tab 1 and switch to tab 2, I lose what I typed in tab one when I click back.
Could somebody help to provide me with a solution to these problems?
One NotepadView for multiple instances of the ViewModel
Keeping information when tabs change
Many thanks in advance.
I moved the Header and Text properties into ViewModelBase and then when I add a ViewModelBase as a Tab I used a DataTemplate for the ViewModelBase as I was for the NotePadViewModels with just a textbox.
<DataTemplate DataType="{x:Type ui:ViewModelBase}">
<TextBox Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DataTemplate>
As apose to using this:
<DataTemplate DataType="{x:Type ui:NotepadViewModel}">
<ui:NotePadView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type ui:NotepadViewModel2}">
<ui:NotePadView2 DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type ui:NotepadViewModel3}">
<ui:NotePadView3 DataContext="{Binding}"/>
</DataTemplate>
Thanks to Rachel for a small hint to the problem. Basically on the previous way nothing was actually binding within the datatemplate so it had no reason to keep it as a resource. By adding a default textbox within a datatemplate I can add as many notepads as I want whilst being able to add other views which don't need a textbox.
By default, WPF will re-use a TabItem when possible and simply replace the DataContext behind it. If your properties are not bound to anything in the DataContext, they will get reset when you switch tabs.
So to get your changes to persist, you need to store them in the DataContext
For example,
<TabControl ItemsSource="{Binding MyNotepadVMCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding SomePropertyOnViewModel}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

WPF implicit datatemplate with observablecollection

I'm new to WPF and using MVVM. I have a view in which I want to display different content according to what a user selects on a menu. One of those things is another user control Temp which has a view model (TempVM) so I am doing this:
<ContentControl Content="{Binding Path=TempVM}"/>
and TempVM (of type TempViewModel)is null until the user clicks a button. Its data template is this
<DataTemplate DataType="{x:Type vm:TempViewModel}">
<view:Temp />
</DataTemplate>
That's fine, but the other thing I want to do is show a listbox when a user clicks a different menu item. So I am trying to do
<ContentControl Content="{Binding Path=Missions}"/>
(Missions is an observable collection of MissionData) and trying to template it like this:
<DataTemplate DataType="{x:Type ObservableCollection(MissionData)}">
<StackPanel>
<ListBox ItemsSource="{Binding}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go"/>
</StackPanel>
</DataTemplate>
But the compiler doesn't like the type reference. If I try doing it by giving the template a key and specifying that key in the ContentControl it works but obviously I see the ListBox and button when there's no Missions. Obviously I could make a user control and viewmodel and follow the same pattern as I did for the TempVM but it seems over the top. Am I going the right way about this and what do I need to do?
From what i see is that you try to use a Collection as a dataobject which is in my opinion bad practice. Having a DataTemplate for a collection is also problematic, like you already have witnessed. I would advice you to use a ViewModel for your missions collection.
class MissionsSelectionViewModel
{
public ObservableCollection<Mission> Misssions;
public MissionData SelectedMission;
public ICommand MissionSelected;
}
and modify your datatemplate to
<DataTemplate DataType="{x:Type MissionsSelectionViewModel}">
<StackPanel>
<ListBox ItemsSource="{Binding Missions}" SelectedItem="{Binding Path=MissionData, Mode=TwoWay}" DisplayMemberPath="MissionName" SelectedValuePath="MissionId" />
<Button Content="Go" Command="{Binding MissionSelected}/>
</StackPanel>
</DataTemplate>
If I were to follow your pattern of implicit templates, I would derive a custom non-generic collection MissionDataCollection from ObservableCollection<MissionData> and use it to keep MissionData items. Then I would simply reference that collection in DataType. This solution gives other advantages like events aggregation over the collection that are useful.
However, it seems to me that the best solution is the following.
Add a IsMissionsListVisible property to your VM.
Bind the Visibility property of the ContentControl showing the list to the IsMissionsListVisible property.
Use a keyed DataTemplate resource.
Implement the logic that determines if IsMissionsListVisible. Supposedly it should be true when there is at least one mission in the selected item. But the logic may be more complex.
I would do it this way. In fact, I do it this way usually, and it gives several benefits. The most important is that I can explicitly control the logic of content visibility in various situations (e.g. async content refresh).

DataBinding in TemplateSelector

I'm using a few data templates to display different values, those data templates are chosen by DataTemplateSelector. Every control has some DataBinding to my custom objects. Objects are part of an ObservableCollection and then DTS is choosing the template for them. The problem is: When I try to run my app with some pre defined objects (in code) the selected controls has no values. Ex:
<!--Date Template-->
<DataTemplate x:Key="DateTemplate">
<WrapPanel x:Name="DateTemplate_Panel">
<WrapPanel.DataContext>
<params:FTParams />
</WrapPanel.DataContext>
<Label x:Name="DateTemplate_Label" Content="{Binding Path=Name}" />
<DatePicker x:Name="DateTemplate_DatePicker" SelectedDate="{Binding Path=SelectedValue}" SelectedDateFormat="Long" />
</WrapPanel>
</DataTemplate>
Controls are responding only when I change their value (INotifyPropertyChanged is implemented)
If I set
<Label Content="{Binding Path=SelectedValue}"/>
and I select a date in DataPicker then the content is loaded correctly. But I really need to have this values loaded on startup.
Can you give me some advice?
The data template should not have embedded data. And you definitely don't want to instantiate instances of FTParams, from within the DT. The DataContext property of the DataTemplate is set implicitly, when you have the data somewhere else in the tree.
I assume you have some sort of ItemsControl, but for the simplicity, let the sample below have a content control:
<ContentControl ContentTemplate="{StaticResource DateTemplate}">
<params:FTParams />
</contentControl>
If you had all your items in ItemsControl (with ItemsSource bind to the ObservableCollection), then instead of ContentTemplate you should set the ItemsTemplate, or if you want to work with template selector, set the ItemTemplateSelector.
<ItemsControl ItemsSource="{Binding PathToTheObsCollectionProperty}"
ItemTemplateSelector="{StaticResource MySelector}" />
In all cases, the DT should not have explicitly set the DataContext property.
Then have your data template without the DataContext element.

Categories