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
}
Related
I am trying to plot multiple LineSeries elements in one <oxy:PlotView /> element in WPF using C# in Oxyplot where I am using the MVVM pattern. Say I have the (hypothetical) situation that I am tracking cars with a position as a function of time. Say I have the following files:
Model
Car.cs
View
DisplayView.xaml
DisplayView.xaml.cs
ViewModel
DisplayViewModel.cs
On initialization I create three car elements (in DisplayViewModel.cs : Cars = new List<Car>{ new Car(), ...};) with their corresponding displacement vs. time data PlotData = new List<DataPoint>{ new DataPoint(0,1), new DataPoint(1,4), ...}; and CarName.
DisplayView.xaml (1):
<ListBox ItemsSource="{Binding Path=Cars}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<oxy:Plot Title="{Binding CarName}" Height="400" Width="500">
<oxy:Plot.Series>
<oxy:LineSeries ItemsSource="{Binding Path=PlotData}"/>
</oxy:Plot.Series>
</oxy:Plot>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This works as expected. It shows a <ListBox /> with the three (separate) graphs. However this is not what I am trying to achieve. I would like to do something like the following:
DisplayView.xaml (2):
<ItemsControl ItemsSource="{Binding Path=Cars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=CarName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This displays the three individual car names as is expected, so I know I have the data source connected correctly. However, wrapping this <ItemsControl>...</ItemsControl> element with <oxy:Plot><oxy:Plot.Series>...</oxy:Plot.Series></oxy:Plot> and changing the <TextBlock /> element to a <oxy:LineSeries /> element and binding its ItemsSource property to the earlier mentioned DataPoint field yields the errors A value of type 'ItemsControl' cannot be added to a collection or dictionary of type 'collection`1'. and The specified value cannot be assigned to the collection. The following type was expected: "Series"..
DisplayView.xaml (doesn't work):
<oxy:Plot Title="Displacement plot">
<oxy:Plot.Series>
<ItemsControl ItemsSource="{Binding Path=Cars}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<oxy:LineSeries ItemsSource="{Binding Path=PlotData}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</oxy:Plot.Series>
</oxy:Plot>
I am new to the MVVM pattern - and C# and WPF programming in general - and I see the DisplayView.xaml.cs code-behind file needs to be empty. I can get the three LineSeries to display in one graph when using the code behind, but I am trying to utilize the power of MVVM. Can someone give me pointers on how I should fix this? I am just not experienced enough (yet) to play around with the code, so some explanation would be appreciated. What files do I need to add, what methods do I need to create and where do I create these? Is this even possible in the way I envision?
Kind regards,
Tom
I think I grasp the concept (after writing down my thoughts and questions in this post), so I am answering my own question. In DisplayView.xaml I added a <PlotView /> as such: <oxy:PlotView x:Name="DisplayPlotView" Model="{Binding PlotModel}" />. I also created a PlotModel in DisplayViewModel.cs, which is fine by the MVVM-pattern, as far as I understand. I loop through Cars to add the different series by doing:
foreach(Car car in Cars)
{
PlotModel.Series.Add(car.PlotData);
}
Where I now made the PlotData a LineSeries in the Car.cs. This shows the three lines in one graph.
I'm working on a WPF page with the following:
<ItemsControl ItemsSource="{Binding Peopl.PhoneNums}" x:Name="PhoneList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0" x:Name="PhoneEntry">
<TextBlock Text="123-456-78901"/>
<ComboBox ...>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There can be multiple stackpanels, each with a unique phone number; in code behind, each phone number has a flag indicating if it should be enabled; I want to be able to enable each entry in the stack panel based on that flag but I'm stuck accessing it....
I have:
foreach (Phone phone in PhoneList.ItemsSource)
{
if (phone.ShouldBeDisabled)
{
int index = PhoneList.Items.IndexOf(phone);
PhoneList.IsEnabled = false;
//this disables the entire control;
// I can't access "PhoneEntry" here... hmm
}
}
Is there a way to disable only one entry at a time? How can I access PhoneEntry? Should I try to disable the each stackpanel entry based on the bound value?
You may invert your view model property and call it ShouldBeEnabled. Now you can bind the StackPanel's IsEnabled property.
<StackPanel ... IsEnabled="{Binding ShouldBeEnabled}">
...
</StackPanel>
In case you can't change the view model, you may use a binding converter that inverts the property value:
<StackPanel ... IsEnabled="{Binding ShouldBeDisabled,
Converter={StaticResource InverseBooleanConverter}}">
...
</StackPanel>
Your Phone class would have to implement the INotifyPropertyChanged interface and fire the PropertyChanged event when the value of the ShouldBeDisabled property changes.
I need to display hierarchical data like:
public class Element
{
public string Name { get; private set; }
public Element[] Elements { get; private set; }
}
It would be just vertical panel with rectangle (with Name) for each element. If element is clicked, its child elements are displayed below it (element is expanded). If one of them is clicked, its elements appear and so on.
I already googled this and found out that there is no HierarchicalDataTemplate and no treeview in WinRT.
So I started to do it by myself.
I created ItemsControl and DataTemplate DataTemplate1 for it. In DataTemplate1 I also create ItemsControl and set DataTemplate2 as ItemTemplate. In DataTemplate2, ItemTemplate is DataTemplate3 and so on. The last DataTemplate is without ItemsControl.
In buttons Click event I change Elements IsVisible property for any elements in DataModel (that is Element[]), so it is easy to perform any custom logic to expand/collapse elements.
<DataTemplate x:Key="DataTemplate2">
<StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button Style="{StaticResource ItemButtonStyle}"
Click="MenuElement_Click">
<TextBlock Style="{StaticResource ItemTextBlockStyle}" Text="{Binding Name}"/>
</Button>
<ItemsControl ItemsSource="{Binding Elements}" ItemTemplate="{StaticResource DataTemplate3}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DataTemplate1">
<StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
<Button Style="{StaticResource ItemButtonStyle}"
Click="MenuElement_Click">
<TextBlock Style="{StaticResource ItemTextBlockStyle}" Text="{Binding Name}"/>
</Button>
<ItemsControl ItemsSource="{Binding Elements}" ItemTemplate="{StaticResource DataTemplate2}"/>
</StackPanel>
</DataTemplate>
It works fine, but the problem is that if I want to enable 10 levels of hierarchy, I have to copypast 10 datatemplates. And 11 level still will not be available.
I also tried to create DataTemplate in C# and manually apply DataTemplate for its ItemSource and so on, in recursive method.
But I found 2 problems.
I don't know actually how to create DataTemplate in metro (C#), because it has no VisualTree property. I can only make (var dt= new Datatemplate();) and I don't know how to change it.
If I read DataTemplate from XAML (var dateTemplateRoot = (DataTemplate)this.Resources["DataTemplate1"];)
I still can't find ItemsControl in it and change its DataTemplate.
Actually, I can use var content = dateTemplateRoot.LoadContent(); and then find ItemsControl by VisualTreeHelper, but I can't use content after that as DataTemplate (content has type DependencyObject).
So, actually I have 2 questions.
Is it a good approach to perform hierarchical dropdown list by "binding" all items and only switch Visibility property?
The second is - how to enable unlimited level of hierarchical nesting?
WinRT XAML Toolkit has a TreeView control now. Check it out: http://winrtxamltoolkit.codeplex.com/SourceControl/changeset/view/b0ee76bd6492#WinRTXamlToolkit/Controls/TreeView/TreeView.cs
Take care though - this is just a rough port from Silverlight Toolkit and might not work so well. Also if you are planning on releasing it as part of a Windows Store application - you would need to heavily restyle it unless your app is desktop-only since it is not very touch-friendly.
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
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.