WPF List of BaseClass Items and their visibility - c#

I have a following problem
public abstract class ParentClass
{
public int Field{get;set;}
}
public class ChildrenClass1 : ParentClass
{
public int Field2{get;set;}
}
public class ChildrenClass2 : ParentClass
{
public int Field3{get;set;}
}
No I have a list of ParentClass.
List<ParentClass> Parents = new List<ParentClass>();
I bind it to Listbox like this.
<ListBox ItemsSource = "{Binding Parents}"/>
And now if it is of type ChildrenClass1 I want to show Field2 and if it is ChildrenClass2 I want to show Field3 in ListBox ItemTemplate.
The first solutions that came to my head is to do something like this:
<ListBox ItemsSource = "{Binding Parents}"/>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text = "{Binding Converter = {StaticResource Converter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And converter should check for the type of item and return Field2 if it is ChildrenClass1 and so on.
But I think it is workaround rather than solution. Is there anything that can check types in xaml or something like this? How do you solve this problem ?
EDIT: What about situation if I want to change dramatically DataTemplate not only one field? The above workaround will be useless. Workaround for this problem is to make 2 ListBoxes with different DataTemplate and set the visibility with converter checking actual typeof item. But it will generate many binding errors and generate 2 listboxes instead of one which slows our application.

You can create two DataTemplate for each type, and WPF play it all magic.
<DataTemplate DataType="{x:Type namespace:ChildrenClass1}">
<TextBlock Text="{Binding Field2}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type namespace:ChildrenClass2}">
<TextBlock Text="{Binding Field3}"/>
</DataTemplate>

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.

Creating a ViewModel for each item of ItemsControl

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

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.

binding an object into a control of wpf

I am new in WPF and I would like to have a quick advice on how to bind this object to a wpf control and I dont know which control should I use:
public class Parent
{
public string Name{get; set;}
public List<Child> Childs {get; set;}
}
public class Child
{
public string Name{get; set;}
public int Age {get; set;}
}
public class ParentFactory
{
public List<Parent> Parents {get; set;}
public ParentFactory()
{
Child child1 = new Child() {Name="Peter", Age=10;};
Child child2 = new Child() {Name="Mary", Age=9;};
Child child3 = new Child() {Name="Becky", Age=12;};
Parent parent1 = new Parent(){Name="Adam", Childs = new List<Child<>(){child1, child2}};
Parent parent2 = new Parent(){Name="Kevin", Childs = new List<Child<>(){child3}};
Parents = new List<Parent>(){parent1, parent2};
}
}
After creating this instance:
ParentFactory parentFactory = new ParentFactory();
I would like to bind the parentFactory.Parents() to a control in WPF. I would expect to see something like this:
Adam
-- Peter, 10
-- Mary, 9
Kevin
-- Becky, 12
They are all displayed on textboxes and I can change them.
Thanks in advance.
Use a TreeView with a HierarchicalDataTemplate.
Note however that without implement INotifyPropertyChanged on your model, your bindings won't update on any property changes. Also, without replacing your lists with ObservableCollections, your view won't update as you add more items to the list.
Something like this should work, first define your templates:
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Parent}" ItemsSource="{Binding Childs}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Child}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock>, </TextBlock>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
Then you can use them with a TreeView like this (assuming Parents is a property in the DataContext of the TreeView):
<TreeView ItemsSource="{Binding Parents}"/>
If you don't want a TreeView, you can easily do something list this with a ListView, change you DataTemplates to this:
<DataTemplate DataType="{x:Type local:Parent}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ListView ItemsSource="{Binding Childs}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Child}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<TextBlock>, </TextBlock>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</DataTemplate>
And then you can bind it like this:
<ListView ItemsSource="{Binding Parents}"/>
Note: You'll probably want to fiddle around with the styles a little because out-of-the-box, this looks a bit crap. You'll probably want to, at least, indent the child ListView (the one defined in the Parent DataTemplate) and get rid of it's border.
Also Note: the StackPanel to layout multiple TextBlocks for the name and age isn't ideal either, but it's quick and dirty. You might want to handle that differently. You could use a (multi) converter to format it, use StringFormat or add another property to your model just for display, or even just override ToString on the child class.
Another Edit
A quick (and ugly) example of using the DataGrid:
<DataGrid ItemsSource="{Binding Parents}" AutoGenerateColumns="False">
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding Childs}"/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
This puts a data grid inside a data grid using the row details template. If you click a row, it'll display the children as row details. If you want details always available, you can remove the RowDetailsTemplate and replace the DataGridTextColumn with a DataGridTemplateColumn and then define a template for you data.
If you don't wish a tree view (which is probably best as you have a hierarchy - though it's a one-level deep only, so you might still have a case for a list)...
you could do something like this (that's a rough outline, you should fill in the dots)...
<ItemsControl ItemsSource="{Binding AllNodes}">
</ItemsControl>
...and have templates defined for each type specifically, like Matt mentioned also...
<DataTemplate DataType="{x:Type namespace:Child}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type namespace:Parent}">
...
</DataTemplate>
...while the AllNodes is a list you need to flatten from the hierarchy you have, you can use this...
var allnodes = Parents.SelectMany(p => new object[]{p}.Concat(p.Childs));
...and expose AllNodes as a property similar to Parents - you just need a View Model with properly implemented INotifyPropertyChanged as suggested.
hope this helps

Categories