Creating a ViewModel for each item of ItemsControl - c#

I have defined a type Board which containes a public property
ObservableCollection<Column> Columns
I would like to display it with use of MVVM pattern.
I created BoardView and bound it to BoardViewModel. BoardViewModel exposes public property Board of type Board.
BoardView contains a control ItemsControl which sets ItemsSource={Binding Board.Columns}.
<ItemsControl ItemsSource="{Binding Board.Columns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" Margin="10" BorderBrush="#9f9f9f" Width="250">
<v:BoardColumnView Background="#e3e3e3" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
BoardColumnView should show properties of Column type and this works good.
My problem is that I want to create a ViewModel for BoardColumn, and instead of showing only properties of Column type I want to show BoardColumnViewModel which would have defined inside a Column property.
How can I achieve that?
thanks in advance!

You could simply define a Columns property in your BoardViewModel that would contain a collection of BoardColumnViewModel. Something like this:
public ObservableCollection<BoardColumnViewModel> Columns { get; private set; }
You will need to initialize this property somewhere in BoardViewModel, for example:
public BoardViewModel(...)
{
...
Columns = new ObservableCollection<BoardColumnViewModel>(Board.Columns.Select(c => new BoardColumnViewModel(c)));
}
And then bind to that property, instead of Board.Columns:
<ItemsControl ItemsSource="{Binding Columns}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" Margin="10" BorderBrush="#9f9f9f" Width="250">
<v:BoardColumnView Background="#e3e3e3" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As a general principle in MVVM is that it is not recommended to bind directly to a model. Instead you should always try to bind to a view model. This is why you have the problem you've described - because you bind to a public property Board, which is your model.

In my understanding is BoardColumnView an own UserControl?
If so just set the DataContext of this UserControl with this.DataContext = new BoardColumnViewModel(); in code behind or use XAML equivalent. Then create in your BoardViewModel an ObserveableCollection<> which holds multiple Instances of BoardColumnView and set the ItemSource to this collection. In your BoardColumnView XAML define your Layout, Bindings, etc. which are displayed in BoardView.
So every time you add a BoardColumnView to the ItemsSource, which is bound to the collection, a new instance of BoardColumnViewModel is getting created.
...That is my understanding of your Problem, but I might be completely wrong :).

Related

Bind a property, inside DataTemplateItem in ListBox, to an outside object in MainPage

I got ListBox with DataTemplate, inside DataTemplate I got another ListBox, trying to bind it's Visibility to another object which is found in the MainPage
XAML:
<ListBox x:Name="RegistersListView" ItemsSource="{x:Bind registersList}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="structures:Register">
<StackPanel>
<ListBox x:Name="FieldsListView" ItemsSource="{x:Bind fields_list}" Visibility="{x:Bind SomeVisibilityObjectIMain}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="structures:Field">
<Button Content="{x:Bind name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#:
public sealed partial class HWTab : Page
{
public ObservableCollection<Register> registersList = new ObservableCollection<Register>();
public var SomeVisibilityObjectIMain;
public HWTab()
{
InitializeComponent();
InitData();
this.DataContext = hwType;
}
....
}
I need to bind to "SomeVisibilityObjectIMain" somehow, I tried to bind with ElementName or even make object static, but could not succeed.
My bindable object is more complex than the example here but solve this will give me the way for solution.
You could use {Binding} instead of x:Bind. This way you could add a x:Name="Page" to your page and then use this name in the inner binding:
{Binding ElementName=Page, Path=MyProperty}
For {Binding} to work however, MyProperty must be actually a property. From your sample code (which uses var which is also invalid) it seems it is just a plain field, so you will need something like:
public string MyProperty {get;set;}
To also get PropertyChanged notifications, you will need to add a backing field and trigger PropertyChanged event.
However, overall a better solution would be to include all information a DataTemplate needs into the actual items which are bound to it. That means - you would create a custom view model type for the items, which would include the information that you need to control visibility.

It is possible to Have ObservableCollection of ObservableCollections in viewModel using MVVM pattern

I wonder to know if it is possible to have an ObservableCollection of ObservableCollections in viewModel like this:
ObservableCollection<ObservableCollection<EditingMetadataViewModel>> MetadatasList = new ObservableCollection<ObservableCollection<EditingMetadataViewModel>>();
each ObservableCollection shows a list of metadata when it is binded to the view. In the case I have more than one file selected I want to have the same metadata lists number as selected files number (e.g if I select three files and I want to edit their metadata, I want to have three lists of metadata list).
Yes you can create a ItemsControl(or Panel controls) of ListView, ListBox, Grid, ... as child items:
<ItemsControl ItemsSource="{Binding MetadatasList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding MetaData}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I don't see why not and would be very easy for you to verify this. You could quickly create a ListBox that has a data template that contains a ListBox.
For clarity however you may want to define a class FileMetadata. In that case the first observable collection will be declared as
public ObservableCollection<FileMetadata> FileMetadataList {get; private set; }
Class FileMetadata would contain a member:
public ObservableCollection<EditingMetadataViewModel>> MetadatasList {get; private set; }
This is equivalent with your code but will may make certain parts easier to read and manage.

Caliburn.Micro: Grid not getting binded/linked to x:Name

So I'm developing a Windows Phone 8 app with the Caliburn.Micro framework. I'm trying to create a grid where I, at runtime add/remove elements such as TextBlock's at runtime. I've tried a few things to bind my code to the x:Name but nothing has worked so far.
So one of the things i tried was having a placeholder grid in my xaml aka View:
<Grid x:Name="ContentPanel" Margin="0,97,0,0" Grid.RowSpan="2">
</Grid>
And then i my ViewModel i use the following to bind my ContentPanel Grid:
private Grid contentPanel;
public Grid ContentPanel
{
get
{
return contentPanel;
}
set
{
contentPanel = value;
NotifyOfPropertyChange(() => ContentPanel);
}
}
I then created a TextBlock to add to the grid:
TextBlock txt1 = new TextBlock();
txt1.Text = "2005 Products Shipped";
txt1.FontSize = 20;
txt1.FontWeight = FontWeights.Bold;
Grid.SetRow(txt1, 1);
And finally i added the TextBlock to my Grid:
ContentPanel.Children.Add(txt1);
When i run this code ContentPanel turn out to be equals null, why is that? Shouldn't Caliburn auto bind ContentPanel x:Name="ContentPanel" with the property ContentPanel?
I would appreciate your help in this matter.
My core problem, that i need solved is this:
I got a login page in my app where i show some pictures and text loaded from a server. As you can see below this is done with Image and a TextBlock When that server is offline or the wi-fi simply aren't enabled i want to replace this picture+text with a static image. Aka i want to remove the TextBlock from the StackPanel.
The part where i load and show the stuff form my server works great and looks like this in my xaml:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
So when the server is offline/wifi disabled i want to replace that with. so that the TextBlock is no longer there:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
Is this even possible? If not what would the best semi-solution be?
EDIT 1: I've managed to setup the flow following the instructions from the accepted answer. But my BooleanToVisibilityConverter is not called, though my NotifyOfPropertyChange(() => IsConnectionAvailable); is getting called.
My Property:
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
How i change the bool: This code is called in my constructor for my ViewModel(just as a test to see if it was working):
IsConnectionAvailable = false;
TextBlock (without trigger code cause its the same as previous):
<TextBlock Text="{Binding Title}" Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BoolToVisibility}}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
It's like the Binding IsConnectionAvailable isn't working because i can change the name IsConnectionAvailable in my Xaml to anything and my NotifyOfPropertyChange(() => IsConnectionAvailable); will still be called.
Any ideas?
I can't even do a normal bind Visibility="{Binding Path=IsVisibil,Mode=TwoWay} to a public Visibility IsVisibil property. I've done this in other classes, but even this won't work??
EDIT 2: The problem that course the binding not to work, seems to lie somewhere in this code:
<StackPanel Orientation="Horizontal" Background="White" DataContext="{Binding FeedItemsAnnounce,Mode=TwoWay}" >
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" Margin="5" Width="170" Height="138">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
<TextBlock Text="{Binding Title}" Visibility="{Binding Path=IsVisibil,Mode=TwoWay}" TextWrapping="Wrap" Width="160" Foreground="Black" FontSize="24" VerticalAlignment="Center" Margin="25,0,0,0"></TextBlock>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAnnouncement">
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
Solution to EDIT 1 and 2: I created an x:Name"Root" at the top of my xaml structure. Then changed the binding to:
ElementName=Root, Path=DataContext.IsVisibil
This is needed because the binding to visibility that I'm trying to set is inside another DataContxt.
This isn't the correct way to use CM, there are a number of areas where you are confusing the model and viewmodel and the binding functionality in CM.
What you are doing currently
You are attempting to have the CM framework look for a property called ContentPanel on your ViewModel and automatically figure out what properties on Grid to bind it to...
This won't work because of a few reasons:
I don't think there is a convention for Grid in CM - it's not really bindable in an obvious way (it's a layout container)
Grid is not a data enabled control - it doesn't know how to consume a collection and display dynamic rows out the box (it's a layout container)
What you are doing doesn't really make any sense (you have an instance of a grid in your UserControl and you have also instantiated a grid in your ViewModel - these are two separate instances of a control - you can't 'bind' them together - that's not how it all works)
CM and Bindings
When you using element name bindings e.g. x:Name with CM, it attempts to find a property on the ViewModel which matches the element name. At this point, depending on the conventions setup for the source control in question, CM will attempt to automagically wire up all the bits and pieces.
There are default conventions contained in ConventionManager which determine which properties to bind when you use element name bindings - e.g. for TextBlock, the Text property on the TextBlock is bound to the target property on the ViewModel.
http://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro.Platform/ConventionManager.cs - look at the class constructor on ConventionManager to see the out of the box conventions - there isn't one for Grid
Once a target property is found, CM will bind it up.
(As an aside: it's worth noting that if the control type is a ContentControl CM will do some composition magic so you can have viewmodels that contain other viewmodels and have a composition all bound up at runtime - great for screens which have multiple sub-windows etc)
The problem you have is that there is no convention setup for Grid out of the box - this is most likely because a Grid in SL/WPF is primarily used for layout, and is not really a 'data container' or data aware in any way (apart from the few dependency properties you can bind to) - i.e. I don't think it's possible to bind to a grid and get a dynamic number of columns/rows without some customisation to the control, hence the omission of any conventions
(think about it - if you are binding a grid to a collection, what should the grid do... add rows or columns? It can't really be supported in a sensible way)
Now bringing it back to SL/WPF for a sec:
Usually if you want a variable list of items you will need to bind to the ItemsSource property of a control which inherits from ItemsControl (or ItemsControl itself).
Many controls do this: if they need to display a dynamic number of items they will usually inherit from ItemsControl.
How does this tie in with CM?
Caliburn Micro knows how to bind up ItemsControl out of the box. This means you can have a property on your ViewModel containing a collection of items and after binding you get a dynamic view of these at runtime
For example - a CM bound ItemsControl might look like this:
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeTextualProperty}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now you just need a collection of objects to bind this to - each item in the collection becomes a new item in the control with its DataContext pointing to the bound item. I've made the assumption that you would want each item to be a ViewModel which contained the property SomeTextualProperty - I've defined that here...
// Provides a viewmodel for a textual item
public class TextItemViewModel
{
public string SomeTextualProperty { get; set;}
}
The VM that should contain the list of items would need to have a collection to bind against.
(Note: Since you are adding items to it at runtime you need to tell the UI when the collection changes - ObservableCollection gives you this for free as it implements collection changed notification events)
// This is the viewmodel that contains the list of text items
public class ScreenViewModel
{
public ObservableCollection<TextItemViewModel> TextItems { get; set; }
}
What else I would consider the incorrect approach
Your ViewModels shouldn't know about your View implementation i.e. they shouldn't reference any type of controls unless absolutely necessary (I can't think of a time when I had to put a control in a VM). ViewModels should model the view - but they shouldn't really need to know any specifics about what that view contains - this way they are more easily testable and they are easily reused
If you follow the above approach, you can get away with providing an application which re-uses the set of viewmodels, but provides different views for each. You can try this by replacing ItemsControl with another type of control in the view (as long as it's data aware such as a datagrid) and the VM will still work - the VM is view agnostic.
Your use of Grid in your VM is not ideal because Grid is a visual control, it is not data. Remember that the visuals are your View and the ViewModel should just contain data and events which notify the view of things happening
If I was doing this - the code would look more like the code I posted above.
To sum up
Model the information you wanted to show in a ViewModel (TextItemViewModel)
Add a collection of these objects to the main ViewModel (ScreenViewModel) using a change aware collection such as ObservableCollection
Add/remove items from the collection using the standard add/remove
Bind the ItemsControl in the view using x:Name bindings to the collection on your ScreenViewModel
Adding/removing items in the VM will fire property changed notifications. ItemsControl will watch for these events and update itself accordingly
Addendum
You could get away with just using an ObservableCollection<string> instead of a TextBlockViewModel but it's not clear if you want to add more properties to the items you are binding to the grid (such as IsHeading property for headings which you could then make bold/italic in the view)
If you want to just use strings just modify the DataTemplate to bind directly to the DataContext rather than a property on the DataContext
<ItemsControl x:Name="TextItems">
<!-- host the items generated by this ItemsControl in a grid -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- render each bound item using a TextBlock-->
<ItemsControl.ItemTemplate>
<DataTemplate>
**<TextBlock Text="{Binding}"/> <!-- Bind direct -->**
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
Ok in your case it's quite simple - your ViewModel should simply model the state of the server:
public class LoginPageViewModel
{
public bool IsConnectionAvailable { get; set; } // or whatever your variable should be called
}
Then bind the visibility of the textblock to this using a converter:
<TextBlock Visibility="{Binding IsConnectionAvailable, Converter={StaticResource BooleanToVisibilityConverter}}">
You will need to declare the static resource for the converter somewhere (in the control itself or your main resources dictionary for example)
It looks like there is a converter already defined in System.Windows.Controls somewhere, but in case you can't find it the implementation is pretty simple (you could probably do this a bit better to guard against invalid input but for brevity I've kept it tiny):
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture)
{
throw new NotImplementedException();
}
}
You may also want to change the state from available/unavailable during the views lifecycle, so in that case you probably want to use the property changed events built in to PropertyChangedBase (which Screen also inherits) to let the view know when the property changes
private bool _isConnectionAvailable;
public bool IsConnectionAvailable
{
get { return _isConnectionAvailable; }
set
{
if (_isConnectionAvailable != value)
{
_isConnectionAvailable = value;
NotifyOfPropertyChange(() => IsConnectionAvailable);
}
}
}
Addendum 2
I prefer the terse CM syntax instead of being explicit when binding action messages - so your XAML would change from:
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128">
<i:Interaction.Triggers>
<i:EventTrigger
EventName="Tap">
<cm:ActionMessage
MethodName="LoadAdvertisement" >
<cm:Parameter Value="{Binding Link}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image>
To
<Image delay:LowProfileImageLoader.UriSource="{Binding ImagePath,Mode=TwoWay}" DataContext="{Binding FeedItemsAdvertisement,Mode=TwoWay}" Margin="0,20,0,39" Width="380" Height="128" cal:Message.Attach="[Tap] = [LoadAdvertisement($dataContext.Link)]"></Image>
(actually that might not be right with the $dataContext.Link part ... but then again it might be... see here: http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Actions&referringTitle=Documentation)

WPF organizing data templates

I'm writing a WPF application. I want it to display data in ListBox from different sources. I want to make some common source interface like
interface IDataSource<T>
{
ObservableCollection<T> Elements { get; set; }
DataTemplate ElementDataTemplate { get; set; }
}
But I don't know which is the best type or types which I should user for IDataSource. I can make it UserControl, but it seems to be unnecessary, because my DataSource is not user control. The main problem is with ElementDataTemplate. How can I properly manage it not from UserControl class? Should I care another helper UserCntrol class and call something like (new MyUserControl).FindResource("ElementsDataTemplate") to obtain datatemplate or there is more fine way to keep and get DataTemplate?
You can simply apply a data template for a specific type in the resource section of the corresponding view:
<!-- Items may be of type ViewModel1 and ViewModel2 -->
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type vm:ViewModel1}">
<TextBlock Text="{Binding PropertyA}" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ViewModel2}">
<TextBlock Text="{Binding PropertyB}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
So there is no need for the interface.

ItemsControl bound to ICollectionView doesn't load correct View

I have a main view whick looks like this:
<ItemsControl Name="Records" />
in ViewModel of this View there's a:
public BindableCollection<RecordViewModel> Records { get; private set; }
and there's also (empty) RecordViewModel and simple RecordView.xaml
In this configuration everything's working fine. The view RecordView shows automatically for every record I add to Records Collection.
But now I'd like to add filtering to it.
So I've created a filter and now need to bind to ICollectionView instead of BindableCollection<RecordViewModel>.
The deal is that Caliburn.Micro doesn't show any view no more, but instead shows MyNamespace.RecordViewModel text in place of where the item's view should be.
How may I fix this?
Ok, I got it
The view should look like this:
<ItemsControl Name="Records">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It's working now.
But if there's a shorthand for this - let me know.
I used
<ItemsControl.ItemTemplate>
<DataTemplate">
<ContentControl x:Name="Records" />
</DataTemplate>
</ItemsControl.ItemTemplate>

Categories