So i have this ViewModel class:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Porsons
{
get { return _persons; }
set
{
_persons = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And then create this ViewModel class and populate its Person list:
ViewModel viewModel;
ObservableCollection<Person> persons
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
viewModel.Porsons= persons;
}
And then my ListView:
<ListView ItemSource={Binding Persons}/>
So instead of binding this Persons list into my ViewModel class and then do this ItemSource can i do it in pure XAML or this is the right way ?
Instead of creating a ViewModel property on your view it is recommended to use it's DataContext (this link also shows how to set it using XAML). Also don't populate the view model in the view since most of the time the data resides in the model and the view should not know anything about any models (when following MVVM).
Please read the link above and visit the links you meet. Also read this article about MVVM. This gives you some basic knowledge to make it easier to understand how to use the WPF framework.
There are many variations of view model creation in XAML.
For example alternatively you can create it in the App.Xaml to make it globally accessible via the StaticResource markup extension and assign it to the individual controls's DataContext via a Style or use an ObjectDataProvider.
This example uses XAML Property Element declaration to create a ViewModel instance directly in the target view. This instance is locally accessible only.
ViewModel.cs:
namespace Example
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.Persons = new ObservableCollection<Person>();
}
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons
{
get => _persons;
set
{
_persons = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View.xaml:
<Window x:Class="Example.MainWindow"
...
xmlns:local="clr-namespace:Example">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<ListView ItemSource={Binding Persons}/>
</Grid>
</Window>
Yes, you can. But no, you most certainly do not want to.
To answer your question, let's say your Person class looks like this:
public class Person
{
public string Name { get; set; }
}
You can easily declare a list in XAML and bind it to a ListView (say) like this:
<ListView DisplayMemberPath="Name">
<ListView.ItemsSource>
<x:Array Type="{x:Type vm:Person}">
<vm:Person Name="Tom" />
<vm:Person Name="Dick" />
<vm:Person Name="Harry" />
</x:Array>
</ListView.ItemsSource>
</ListView>
The result of which is this:
Just because you can do this, though, doesn't mean you should. The whole point of MVVM is to separate your view layer from your view model layer. You should be able to run your entire application from a test build without creating a single view object at all. In asking this question what you are apparently trying to do is declare a data structure in your view layer, which is totally the wrong place to put it. Your view layer should be as "dumb" as possible, with only the weakest possible bindings to your view model layer where the actual logic is going on.
Related
Can someone give me a little help with this one please
I'm trying to reorganise an app to MVVM and make better use of data binding, but am struggling with a little issue.
I have a viewmodel class
public class MainWindowViewModel
{
public ObservableCollection<DiagramElement> Elements { get; set; }
public MainWindowViewModel()
{
AppMachineList = new ListOfMachines();
Elements = new ObservableCollection<DiagramElement>();
}
}
in which I create an observablecollection of the DiagramElement class.
public class DiagramElement : Button
{
private Item linkedItem;
public Item LinkedItem
{
get { return this.linkedItem; }
set
{
this.linkedItem = value;
this.DataContext = this;
this.Template = (ControlTemplate)FindResource("ItemTemplate");
}
}
The DiagramElement class just extends the button class and adds its own controlTemplate.
Back in my MainWindow.xaml.cs class, I instantiate the viewmodel and from that, populate a stackpanel in MainWindow.xaml from the ObservableCollection.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
vm.LoadMachines();
foreach(DiagramElement d in vm.Elements)
{
ItemList.Children.Add(d);
}
}
}
<StackPanel x:Name="ItemList" Orientation="Vertical"></StackPanel>
What I want to do is, do away with the foreach loop and the calls to ItemList.Children.Add(). And replace this with a binding to Elements in the viewmodel like below.
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
vm.LoadMachines();
this.DataContext = vm;
}
}
<StackPanel x:Name="ItemList" DataContext="{Binding Path=Elements}"</Stackpanel>
I can't get the elements to be added to the Stackpanel, the binding doesn't work. Any help gratefully received.
FYI, having a ViewModel with a collection of UI elements (in your case, buttons) violates the principles of MVVM -- the UI and model should not be co-mingled like this.
But the immediate problem is you cannot use a StackPanel -- it is a control container but does not support binding to lists of items. You need to use some kind of repeater like an ItemsControl.
<ItemsControl ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- your DiagramElement should go here, something like
<DiagramElement LinkedItem={Binding Path=SomePropertyOnYourRevisedElement} />
-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But there's more work to be done. DiagramElement needs to have LinkedItem converted into a DependencyProperty (lots of examples of this online) and Elements needs to be a list of some sort of model object that just stores the properties needed for the DiagramElement (with no UI stuff).
I am doing a simple WPF application using MVVM and I am having trouble binding to the SelectedItem property of the combobox.
The setter of the bound property does not get called, and there is no output in the debug windows telling me it is not able to bind (I assume it is able to).
This is .NET 3.5, I made a small example that has the same problem.
In XAML:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox IsDropDownOpen="False" IsReadOnly="False" ItemsSource="{Binding Path=Printers}" SelectedIndex="0" SelectedItem="{Binding Path=Printer.SelectedPrinter, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Name="cmbPrinters" />
</StackPanel>
</Window>
View code behind:
using System.Windows;
public partial class Window1 : Window
{
ViewModel viewmodel;
public Window1()
{
viewmodel = new ViewModel();
this.DataContext = viewmodel;
InitializeComponent();
}
}
View model:
using System;
using System.Collections.ObjectModel;
public class ViewModel
{
public ViewModel()
{
Printers = new ObservableCollection<string>() { "test", "test2" };
Printer = new PrinterViewModel();
}
public PrinterViewModel Printer { get; set; }
public ObservableCollection<string> Printers { get; set; }
}
PrinterViewModel:
using System.Windows;
using System.Diagnostics;
public class PrinterViewModel : DependencyObject
{
public string SelectedPrinter
{
get { return (string)GetValue(SelectedPrinterProperty); }
set
{
SetValue(SelectedPrinterProperty, value);
Debug.WriteLine("!!!!!! SelectedPrinter setter called");
}
}
public readonly DependencyProperty SelectedPrinterProperty =
DependencyProperty.Register("SelectedPrinter", typeof(string), typeof(PrinterViewModel), new UIPropertyMetadata());
}
Can anyone see what I am doing wrong here?
The problem here is that you have a misunderstanding about how the Silverlight dependency property system works.
When the value of a dependency property changes, Silverlight doesn't go through the properties you've defined (such as SelectedPrinter) to set the value of the dependency property. The Silverlight dependency property mechanism keeps track of all of the values of dependency properties, and when the value of one of these properties changes, Silverlight changes its value directly without calling your code to do so. In particular, it will not call your property setter. This should explain why your debugging message wasn't appearing.
The getter in a property that uses a dependency property, such as your SelectedPrinter property, should contain only a call to GetValue, and the setter should contain only a call to SetValue. You shouldn't add any code to the getter or setter, as doing this will not achieve what you want.
Furthermore, you are using dependency properties in the view-model layer. This is not where they are intended to be used. Dependency properties are only intended to be used in the view-layer. Your view-model classes should instead be implementing INotifyPropertyChanged rather than extending DependencyObject.
It is possible to bind two dependency properties together. This is permitted, and occasionally it comes in useful to wire together two dependency properties in the view-layer. In fact, the bindings in your example were working, which explains why you weren't getting any messages about problems with bindings.
why inherit from DependencyObject when doing mvvm?`
public class PrinterViewModel : INotifyPropertyChanged
{
private string _selected;
public string SelectedPrinter
{
get { return this._selected; }
set
{
_selected= value;
OnPropertyChanged("SelectedPrinter");
}
}
}
now your code should work
public class ToolBarView : ToolBar
{
public ToolBarView()
{
this.DataContext = new ToolBarViewModel();
}
}
public ToolBarViewModel: ViewModelBase
{
public ObservableCollection<ViewModelBase> Items {get;set;}
public ToolBarViewModel()
{
// populate button view models
Items.Add(new ButtonViewModel() {Content="Button1"});
Items.Add(new ButtonViewModel() {Content="Button2"});
}
}
public class ButtonView : Button
{
public ButtonView()
{
this.DataContext = new ButtonViewModel();
}
}
public class ButtonViewModel : ViewModelBase
{
public object Content {get;set;}
}
In MainWindow.xaml
<Window.Resources>
<DataTemplate x:Key="buttonTemplate" DataType="{x:Type vm:ButtonViewModel}">
<v:ButtonView Content={Binding Content}/>
</DataTemplate>
<v:ToolBarView ItemsSource="{Binding Items}"
ItemTemplate={StaticResource buttonTemplate}/>
Note: I did INotifyChanged in ViewModelBase class
In MainWindow.xaml. i think My template is wrong.ButtonView in DataTemplate is creating a new view instance. It is not binding the viewModel that was poplulated in the ToolBar Items collection. I tried to do with Relative Binding. Still not successful.
Please help me out.
Just drop the line where you create a new VM and overwrite the DataContext:
this.DataContext = new ButtonViewModel();
Then the DataContext will be inherited (it will be the item in the collection, the ButtonVM).
(As a side-note, you seem to try view-first and view-model-first at the same time, you should stick with one. Also the view should probably already bind to all the relevant properties on the view-model so that you just need need to create the view and that's it)
I want to separate my user interface from my code, so I (obviously) landed at bindings. As a test, I've written the following XAML:
<Window x:Class="BindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="Auto" Width="200">
<StackPanel>
<TextBox Text="{Binding Item}"/>
<Button Content="Add" Click="AddNew"/>
<ListBox Height="100" ItemsSource="{Binding Items}"/>
</StackPanel>
</Window>
The C# looks like this:
namespace BindingTest
{
public partial class MainWindow : Window
{
public string Item { get; set; }
public ObservableCollection<string> Items { get; set; }
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<string>();
}
private void AddNew(object sender, RoutedEventArgs e)
{
Items.Add(Item);
}
}
}
What I want to happen is that the text entered into the textbox is added to the listbox's itemssource. However, this doesn't happen...
Two things you need two do -
Set - DataContext = this; in your constructor.
You'd be better off if you would change your properties to dependency properties instead. You could do that easily with the "propdp" snippet in visual studio.
Data binding is performed against the current data context. However, you have not set the data context for your window. Often you will set the data context to a view model but in your case you simply want to use the window class for that.
You should add the following line to the constructor:
DataContext = this;
Change your code to this:
public partial class MainWindow : Window
{
public string Item { get; set; }
public ObservableCollection<string> Items { get; set; }
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<string>();
DataContext = this;
}
private void AddNew(object sender, RoutedEventArgs e)
{
Items.Add(Item);
}
}
}
You do need to set your DataContext - works for me.
Two things:
You should set the correct data context for your window. Otherwise the binding will not find your properties.
You should initialize your Items collection before the InitializeComponent() call as inside it the ListBox tries to evaluate the expression and get NULL as the binding souce. And since you are not implementing INotifyPropertyChanged and the property is not a DependencyProperty the ListBox will never reevaluate the binding thus it will never get the instance of your Items collection.
So, the code should be as follows:
public MainWindow()
{
Items = new ObservableCollection<string>();
DataContext = this;
InitializeComponent();
}
Try this
hope this will work. But this is not hte right approach. You need to set the DataContext to the Object whose properties u guna use for binding. you must follow MVVM Architecture.
I am trying to bind a list box to a collection. The problem is that the collection can change, but the collection doesn't implement IObservableCollection. What is the best way to force the binding to refresh?
As Tormod suggested, the preferable methods would be changing the collection to an ObservableCollection, or implementing INotifyCollectionChanged in the collection would take care of refreshing the UI.
However, if those options aren't available, then you can 'force' a refresh by using INotifyPropertyChanged in whatever class contains the collection. We then will be treating the list just like a regular property, and using the setter to notify on changes. To do this it requires re-assigning the reference, which is why using something like an ObservableCollection is preferred, as well as raising the PropertyChanged event.
Here is a quick sample showing how this can be done with just a standard generic List:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
this.Names = new List<string>() { "Mike", "Robert" };
this.DataContext = this;
}
private IList<string> myNames;
public IList<string> Names
{
get
{
return this.myNames;
}
set
{
this.myNames = value;
this.NotifyPropertyChanged("Names");
}
}
private void OnAddName(object sender, RoutedEventArgs e)
{
Names.Add("Kevin");
Names = Names.ToList();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Xaml:
<Window x:Class="Sample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300">
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding Names}" />
<Button Content="Add Name"
Click="OnAddName" />
</StackPanel>
</Grid>
Without more information on how and where this collection is used, here are some pointers which may help you.
If the collection is not sealed, you could inherit it.
If the collection is sealed, you could create an adapter class which contains an instance of your collection and wraps all relevant methods.
In any case, your new class could implement IObservableCollection and be used for binding.
You can set a binding to update explicitly and then trigger an update through code by say having a refresh button for example.
As an example.
<StackPanel>
<ListBox
x:Name="lb"
ItemsSource="{Binding SomeList, UpdateSourceTrigger=Explicit}"
/>
<Button Content="Refresh" Click="Refresh_Click" />
</StackPanel>
private void Refresh_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = lb.GetBindingExpression(ListBox.ItemsSourceProperty);
be.UpdateSource();
}
You can also force a refresh in your ViewModel. This is sth I've seen Josh Smith do in his MVVM demo app:
ICollectionView coll = CollectionViewSource.GetDefaultView(myCollection);
if (coll!=null)
coll.Refresh();
myCollection can be any type of collection that you have bound to the View.
Bea Stollnitz has a bit more information about CollectionViewSource:
http://www.beacosta.com/blog/?m=200608