MVVM binding content control from observablecollection of views - c#

I am attempting to populate a ScrollerViewer control with an arbitrary number of of UserControls (Views) whilst using the MVVM pattern and bindings.
I am using an ObservableCollection to maintain my View collection and I have this collection set as the datacontext for my ScrollViewer control, however, getting the views to appear in the scroll viewer has had me going round in circles for a while now.
Can someone please point me to either a suitable example, or kindly provide an example which demonstrates the functionality I am attempting to achieve here?
Many thanks,

First off, I think you want an ItemsControl, not a ScrollViewer. Once you do that, assuming that your ObservableCollection of viewmodels is called "Items":
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<uc:MyControl DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Replace the <uc:MyControl DataContext="{Binding}"/> with a reference to your UserControl.

Related

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>

stackpanel with usercontrol and itemsource shows newitemplaceholder

Im about to create a diagram designer, and have create nodes and edges as usercontrols.
I have an stackpanel where I want to place them. I have managed to make it works with the following code:
<Window.Resources>
<DataTemplate DataType="{x:Type Model:Node}">
<Canvas>
<View:NodeUserControl></View:NodeUserControl>
</Canvas>
</DataTemplate>
</Window.Resources>
<StackPanel Name="DisplayArea">
<ItemsControl ItemsSource="{Binding Nodes}" >
</ItemsControl>
</StackPanel>
Where Nodes is an observablecollection
But this also shows an {NewItemPlaceholder} text and I can't figure out why. Would really appreciate if someone could point out my mistake.
EDIT: I have tried to create a new solution with just the beforementioned code and this doenst show the [NewItemPlaceholder}. Now Im really confused can't see the difference and what else that would cause this.
I got the same issue and the cause was that die source data was bound twice: One time to a DataGrid control and a second bound to an ItemsControl. The ItemsControl shows its items on a canvas and also {NewItemPlaceholder} becomes visible there.
The solution was to avoid adding new items to the DataGrid by:
ok. I didn't need adding.

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).

Parent-Child inside DataTemplate

In my MainWindow, I've got a ListBox whose ItemsSource is bound to an ObservableCollection of Layout POCO objects:
<ListBox x:Name="ListBox" ItemsSource="{Binding Layouts}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Also, in the MainWindow, we define the visual representation of a Layout object using a UserControl (LayoutUserControl):
<DataTemplate DataType="{x:Type local:Layout}">
<local:LayoutUserControl />
</DataTemplate>
When we add objects to the Layouts collection, we see new LayoutUserControls populate the ListBox. This is pretty straight forward.
Now, what I'd like to do, is somehow communicate from the MainWindow to the individual LayoutUserControls. Specifically, from the MainWindow, I want to call a single method on each of the LayoutUserControls... If I attempt to iterate through the ListBox's Items collection, all I get is a reference to the Layout objects, not the LayoutUserControls. Since the LayoutUserControls are defined in a DataTemplate, I don't have a named reference to access them...
Is there a WPF construct that supports this type of interaction from parent to child controls? RoutedEvents were my first thought, but they only support child to parent (bubble) communication. Perhaps commands are the way to go?
Any help would be appreciated.
Thanks!
Yes, there is a method which you should never use, it's the ItemContainerGenerator.
You should, as you noted yourself, set up the communication differently, commands sound reasonable. Expose a command on the Layout and bind the UserControl to it. Or create an event and make the UserControl subscribe to it, then you can raise that.
In Silverlight there is an extension called GetItemsAndContainers as described in this SO question that does this but I cannot find an equivalent in WPF.
However it may still be possible as described in How can I access the ListViewItems of a WPF ListView? by using the VisualTreeHelper to get the LayoutUserControls inside the ListBox.

WPF Datatemplating an ItemsControl

I have an ItemsControl whose ItemsSource gets bound to an ObservableCollection<Component> at run time. I have defined a data template for type Component which works fine.
Now Component has a ObservableCollection<Control> and I want to add another ItemsControl inside my Component Datatemplate to render all the controls. Control here is my own custom object not related to a wpf control.
There are different types of controls so I am trying to use an ItemTemplateSelector to select the right template for each type. In the example below to keep it small I have only shown one of the templates "RWString" which I find using a FindResource in MyControlTemplateSelector overriding SelectTemplate. But the SelectTemplate never gets called(using a breakpoint to check). Is there something wrong in my xaml?
<ItemsControl.Resources>
<src:MyControlTemplateSelector x:Key="XSelector" />
<DataTemplate DataType="{x:Type src:Component}" >
<Expander Visibility="{Binding Path=Show}">
<ItemsControl ItemsSource="{Binding Path=Contrls}"
ItemTemplateSelector="{StaticResource XSelector}">
<ItemsControl.Resources>
<DataTemplate x:Key="RWstring" >
<TextBlock Text="{Binding Path=Label}"/>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Expander>
</DataTemplate>
</ItemsControl.Resources>
Update: Contrls is not a typo, its just me using a silly naming system. Contrls is a property of Component of type ObservableCollection<Control>. Also the reason I am trying to use the the ItemsTemplateSelector is that the ObservableCollection<Control> contains objects of generic types like Control<int> Control<string> etc all deriving from Control and apparently you cant create datatemplates referring to generic types.
Update3: Removed update 2 as it was unrelated. I got the ItemTemplateSelector to work by changing StaticResource to DynamicResource. But I don't know why this works...
I'm guessing this doesn't work with a StaticResource as the Resource is inside the ItemsControl which probably has not been created at load time when StaticResources are evaluated.
DynamicResources at load time are evaluated to an expression at load time and then evaluated to the correct value when requested.
Try move the Resource outside of the ItemsControl.
In the line where you bind the nested ItemsControl, is the Path correct? It is currently "Contrls", should it be "Controls"?

Categories