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
Related
I am a beginner in C# / xaml and I do not understand everything in the binding, among others if it is necessary to notify or not to refresh the view.
In my code I use XML and XElement to store data. The xml contains one list of items (Properties), each of these items contains two lists (videoStreams and audioStreams).
When I add (or delete) one video the view is not refreshed. After if I add (or delete) one audio the view is refershed. The videos and audios lists are updated.
If, in the xaml code, I reverse audio and video lists, the problem is with audio.
Have you any idea about this problem?
My XML (extract)
<Root>
<Properties name="Node">
<videoNode exclude="false">
<rate minFrameRate="" maxFrameRate="" />
</videoNode>
<audioNode >
<bitrateRange min="" max="" />
</audioNode>
</Properties>
The xaml
<ListBox Name="multiMaster" ItemsSource ="{Binding Elements[Properties]}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBox Name="videoStreams" ItemsSource="{Binding Elements[videoNode]}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="stackVideo" Orientation="Vertical">
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Name="audioStreams" ItemsSource="{Binding Elements[audioNode]}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="stackAudio" Orientation="Vertical">
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and the add function
foreach (XElement childElement in oMMPNode.Descendants("Properties"))
{
if (childElement.Attribute("id").Value == profileId)
{
XElement newVideo = new XElement("videoNode");
childElement.Add(newVideo);
break;
}
}
Yes, it is necessary to notify. You need to map the XML data to a set of ViewModels. These should implement INotifyPropertyChanged, and raise the corresponding event when values are changed, etc.
So, instead of XElement you would use:
public class XElementViewModel:INotifyPropertyChanged
{
public XElementViewModel(XElement data)
{
// initialize properties
}
...
// here you define all needed properties of XElement that you want to expose
}
My theory code:
ScriptContainerUserControl.xaml
<ItemsControl x:Name="ScriptItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBox x:Name="pTB" Text="{Binding PhasePriority}" />
<TextBox x:Name="nTB" Text="{Binding Name}" />
<TextBox x:Name="dTB" Text="{Binding Description}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ScriptContainerUserControl.xaml.cs
public ScriptContainerUserControl() : base()
{
InitializeComponent();
ScriptItemsControl.ItemsSource = PScriptCollection;
}
//PScriptCollecion is of type SynchronizedObservableCollection<ProcessScript>
//ProcessScript has the elements PhasePriority, Name, and Description
Would the code above work for making sure
ScriptItemsControl[i].dTB.Text = PScriptCollection[i].Description?
Or is it not possible to bind like this?
Fenster,
It should definitely work, provided you have getter setter properties implemented for all the three properties in ProcessScript class.
When you use a datatemplate - it means you are setting the datacontext of each element of your itemscontrol to an element of your collection.
so here each Itemcontrol element will look at ProcessScript object and if that object has all three properties , you should see the data.
It is not possible to do it in this way. You do not set Binding actually... To have support for observing a changes on collection you should bind the collection to ItemsSource property of ItemsControl.
Instead of line:
ScriptItemsControl.ItemsSource = PScriptCollection;
try this
ScriptItemsControl.ItemsSource = new Binding("PScriptCollection");
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.
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>
Windows Phone 7.1: How to add/delete items from LongListSelector control?
I am using a LongListSelector control from 'Windows Phone Toolkit'. The control is data bound to a ViewModel inherited from an ObservableCollection. When I try the following code:
MyObject mo = new MyObject("Name", "Description", "Value");
App.MyObjectsViewModel.Add(mo);
The ViewModel does seem to get updated but the LongListSelector does not update? What am I missing?
PS: I am new to Silverlight and WP7 development.
Following the XAML for the LongListSelector and the DataTemplates. The code is pretty much straight out of the Windows Phone Toolkit sample (removed some formatting related code to keep the post small)
<DataTemplate x:Key="groupHeader">
<TextBlock Text="{Binding Key}"/>
</DataTemplate>
<DataTemplate x:Key="groupItemHeader">
<Border>
<TextBlock Text="{Binding Key}"
Foreground="#FFFFFF"
FontSize="{StaticResource PhoneFontSizeLarge}"/>
</Border>
</DataTemplate>
<DataTemplate x:Key="myobjectItemTemplate">
<Grid>
<StackPanel VerticalAlignment="Top" Orientation="Vertical">
<TextBlock Text="{Binding Symbol}"/>
<TextBlock Text="{Binding Value}"/>
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
</StackPanel>
</Grid>
</DataTemplate>
<controls:PivotItem Header="myobjects">
<toolkit:LongListSelector x:Name="myobjectsList"
Background="Transparent"
GroupHeaderTemplate="{StaticResource groupHeader}"
GroupItemTemplate="{StaticResource groupItemHeader}"
ItemTemplate="{StaticResource myobjectItemTemplate}"
GroupViewOpened="LongListSelector_GroupViewOpened"
GroupViewClosing="LongListSelector_GroupViewClosing"/>
</controls:PivotItem>
C# code behind for setting the ItemSource
var myobjectsByClassification = from myobjects in App.MyObjectsLibrary
group myobjects by myobjects.Classification into c
orderby c.Key
select new PublicGrouping<string, MyObject>(c);
this.myobjectsList.ItemsSource = myobjectsByClassification;
My guess is that the grouping code is only being called once somewhere in code behind. So the grouped collection is not updated when you add something to your ViewModel collection. The easiest way to handle this (but maybe not the most elegant) is to create your own AddItem() method for the ViewModel collection.
class MyViewModelObject
{
void AddItem( MyObject obj )
{
App.MyObjectsLibrary.Add( obj );
MyObjectsByClassification = from myobjects in App.MyObjectsLibrary
group myobjects by myobjects.Classification into c
orderby c.Key
select new PublicGrouping<string, MyObject>(c);
}
}
Bind MyObjectsByClassification to LongListSelector.ItemsSource in XAML, and make sure you notify the LongListSelector of changes to the property by using INotifyPropertyChanged.
By using LINQ, the object you actually assign to ItemsSource is an IEnumerable<T> not an ObservableCollection<T>. LINQ-to-objects does not support automatic updating via ObservableCollection. After all, it returns a forward-only IEnumerable<T> and not a collection of any kind.
Change your ViewModel to actually expose an ObservableCollection<PublicGrouping<string, MyObject>> and bind your ItemsSource directly to that.