12

I just started learning MVVM and here is what seems to be basic question but I spent whole day trying to figure it out.

I have a solution that contains 3 projects one for Model, one for ViewModel and one for View. The Model contains a class that has 2 properties Text and CheckStatus.

The ViewModel has a list called listOfItems that has three items, each item has these 2 properties from the Model.

The View has a listView inside it there is a CheckBox. What is the proper way to bind the CheckBox content to the property Text?

Here is the model

using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TheModel { public class CheckBoxListModel : INotifyPropertyChanged { private string text; public string Text { get { return text; } set { text = value; RaiseChanged("Text"); } } private bool checkStatus; public bool CheckStatus { get { return checkStatus; } set { checkStatus = value; RaiseChanged("CheckStatus"); } } private void RaiseChanged(string propName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } public event PropertyChangedEventHandler PropertyChanged; } } 

Here is the view model

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections.ObjectModel; using TheModel; namespace TheViewModel { public class TheViewModel { public List<CheckBoxListModel> ListOfItems { get; set; } public TheViewModelClass() { ListOfItems = new List<CheckBoxListModel> { new CheckBoxListModel { CheckStatus = false, Text = "Item 1", }, new CheckBoxListModel { CheckStatus = false, Text = "Item 2", }, new CheckBoxListModel { CheckStatus = false, Text = "Item 3", } }; } public static implicit operator List<object>(TheViewModelClass v) { throw new NotImplementedException(); } } } 

and here is the View XAML

 <UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ctrl="clr-namespace:TheView.Managers" xmlns:TheViewModel="clr- namespace:TheViewModel;assembly=TheViewModel" x:Class="TheView.Styles.ListViewDatabaseStyle"> <UserControl.DataContext> <TheViewModel:TheViewModelClass/> </UserControl.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="100"/> </Grid.RowDefinitions> <Button Content="Continue" Style="{StaticResource ButtonStyle}" Margin="1104,27,40,40"/> <ListView x:Name="listView1" SelectionMode="Multiple" Style="{StaticResource ListViewStyle}" Margin="10,55,10,10" ctrl:ListViewLayoutManager.Enabled="true" ItemsSource=" {Binding TheViewModelClass}" > <ListView.View> <GridView> <GridViewColumn Header="Competency Items" ctrl:ProportionalColumn.Width="1100"/> </GridView> </ListView.View> <ListView.ItemContainerStyle > <Style TargetType="{x:Type ListViewItem}"> <Setter Property="IsSelected" Value="{Binding CheckedStatus}"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListView.ItemContainerStyle> <ListView.ItemTemplate> <DataTemplate> <CheckBox Click="CheckBox_Click" Content="{Binding Path=TheViewModelClass.Text}" IsChecked="{Binding Path=TheViewModelClass.CheckedStatus}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </UserControl> 

Here is the View behind code, I know I shouldn't have something here but where should that part go?

using System.Windows; using System.Windows.Controls; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Controls.Primitives; using System.Windows.Media; using System; using System.Text; using TheViewModel; namespace TheView.Styles { public partial class ListViewDatabaseStyle : UserControl { public ListViewDatabaseStyle() { InitializeComponent(); } public List<string> selectedNames = new List<string>(); private void CheckBox_Click(object sender, RoutedEventArgs e) { var ChkBox = sender as CheckBox; var item = ChkBox.Content; bool isChecked = ChkBox.IsChecked.HasValue ? ChkBox.IsChecked.Value : false; if (isChecked) selectedNames.Add(item.ToString()); else selectedNames.Remove(item.ToString()); } } } 
9
  • The proper way is to make sure the DataContext is set correctly to your Model or ViewModel, and to use a Binding to pull the value from the data object into the UI. Have you encountered the DataContext yet in your learning? If not, I usually send new coders to this post : What is DataContext for? Commented Apr 26, 2017 at 18:43
  • I haven't figured the correct way to use the DataContext so that it works. I have tried several times and I saw it implemented in several way but none worked for me Commented Apr 26, 2017 at 19:22
  • deleted part of the behind code for the datacontext because that was the wrong par. and sorry @Peter your solution didn't work Commented Apr 26, 2017 at 19:30
  • 1
    If i were you i would change list for ObservableCollection. public ObservableCollection<CheckBoxListModel> ListOfItems { get; set; } And trust me. You don't have to add every binding your viewmodel. In your Listview ItemSource={Binding ListOfItems} <CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" /> Commented Apr 26, 2017 at 19:45
  • 1
    By the way - you may find help in the WPF chat room. We're there most weekdays during work hours. Commented Apr 26, 2017 at 20:05

3 Answers 3

9

Here is an easy way which involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them:

For the viewmodel of the main window:

using Wpf = System.Windows; public partial class TestApp : Wpf.Application { protected override void OnStartup( Wpf.StartupEventArgs e ) { base.OnStartup( e ); MainWindow = new MainView(); MainWindow.DataContext = new MainViewModel( e.Args ); MainWindow.Show(); } } 

For all other viewmodels:

This is in MainViewModel.cs:

using Collections = System.Collections.Generic; public class MainViewModel { public SomeViewModel SomeViewModel { get; } public OtherViewModel OtherViewModel { get; } public Collections.IReadOnlyList<string> Arguments { get; } public MainViewModel( Collections.IReadOnlyList<string> arguments ) { Arguments = arguments; SomeViewModel = new SomeViewModel( this ); OtherViewModel = new OtherViewModel( this ); } } 

This in MainView.xaml:

[...] xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff" [...] <local:SomeView DataContext="{Binding SomeViewModel}" /> <local:OtherView DataContext="{Binding OtherViewModel}" /> [...] 

As you can see, a viewmodel can simply be a member (child) of another viewmodel; in this case SomeViewModel and OtherViewModel are children of MainViewModel. Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.

Sign up to request clarification or add additional context in comments.

3 Comments

This is the most underrated post in all of stack overflow. I do this all the time in Xamarin but was unable to piece it together in WPF as the syntax is a little different. Thank you so much
@SQLUser thanks! C-:= glad to be of help. I am amazed by the cumbersome ways in which most WPF applications are written.
lol thanks for that, I'm also reading through the other info and I was quite impressed by the amount of over-complicated stuff just to set goddamn DataContext to my object.
5

First of all. Set dependencies of projects. ViewModel must have access Model. (View and Model projects do not have to reference to other projects.) If i were you i would make a StartUp Project to transfer the control to ViewModel. This "StartUp" project should be WPF, all of others should be "class library" but don't forget to add the required references to projects (For example the system.xaml for your view project to create usercontrols.)

Projects dependencies: - StartUp --> ViewModel; (- ViewModel --> View; or avoid this with DI) - ViewModel --> Model; (I should make another project for interfaces just this is just my perversions.)

StartUp Project: Now in your startup (WPF) project should contains in (app.xaml.cs):

protected override void OnStartup(StartupEventArgs e) { // delete the startupuri tag from your app.xaml base.OnStartup(e); //this MainViewModel from your ViewModel project MainWindow = new MainWindow(new MainViewModel()); } 

The only one thing (Window) in your startup wpf project (to display your UserControls).

MainWindow.xaml content:

<Window x:Class="StartUp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" WindowState="Maximized" WindowStyle="None" AllowsTransparency="True"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Control}"/> </Window> 

(and xaml.cs)

 public partial class MainWindow : Window { public MainWindow(INotifyPropertyChanged ViewModel) { InitializeComponent(); this.DataContext = ViewModel; this.Show(); } } 

And Thats all your StartUp WPF project. In this way we gave the control to your ViewModel project.

(Okay, its just an extra, but i should make a "ViewService" to handle my UserControls)

Interface to find all of View and match the View with ViewModel.

public interface IControlView { INotifyPropertyChanged ViewModel { get; set; } } 

I created a singleton to store and match my views with my viewmodels. (You can skip this part.) I defined this in Model project.

 public class ViewService<T> where T : IControlView { private readonly List<WeakReference> cache; public delegate void ShowDelegate(T ResultView); public event ShowDelegate Show; public void ShowControl<Z>(INotifyPropertyChanged ViewModel) { if (Show != null) Show(GetView<Z>(ViewModel)); } #region Singleton private static ViewService<T> instance; public static ViewService<T> GetContainer { get { if (instance == null) { instance = new ViewService<T>(); } return instance; } } private ViewService() { cache = new List<WeakReference>(); var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where(r => typeof(T).IsAssignableFrom(r) && !r.IsInterface && !r.IsAbstract && !r.IsEnum); foreach (Type type in types) { cache.Add(new WeakReference((T)Activator.CreateInstance(type))); } } #endregion private T GetView<Z>(INotifyPropertyChanged ViewModel) { T target = default(T); foreach (var wRef in cache) { if (wRef.IsAlive && wRef.Target.GetType().IsEquivalentTo(typeof(Z))) { target = (T)wRef.Target; break; } } if(target==null) target = (T)Activator.CreateInstance(typeof(Z)); if(ViewModel != null) target.ViewModel = ViewModel; return target; } } 

And now you have got a "service" to show your UserControls in the mainwindow from your ViewModel:

public class MainViewModel : INotifyPropertyChanged { private IControlView _control; public IControlView Control { get { return _control; } set { _control = value; OnPropertyChanged(); } } public MainViewModel() { //Subscribe for the ViewService event: ViewService<IControlView>.GetContainer.Show += ShowControl; // in this way, here is how to set a user control to the window. ViewService<IControlView>.GetContainer.ShowControl<ListViewDatabaseStyle>(new TheViewModel(yourDependencyItems)); //you can call this anywhere in your viewmodel project. For example inside a command too. } public void ShowControl(IControlView ControlView) { Control = ControlView; } //implement INotifyPropertyChanged... protected void OnPropertyChanged([CallerMemberName] string name = "propertyName") { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(name)); } } public event PropertyChangedEventHandler PropertyChanged; } 

If you don't want to use this "ViewService". Just create an UserControl instance, match DataContext of View with your ViewModel and give this view to Control property. Here is your ViewModel with list (still in ViewMoldel project.)

public class TheViewModel { private readonly ObservableCollection<ISelectable> listOfItems; public ObservableCollection<ISelectable> ListOfItems { get { return listOfItems; } } public ICommand SaveCheckedItemsText{ get{ return new RelayCommand(CollectNamesOfSelectedElements);} } public IEnumerable<ISelectable> GetSelectedElements { get { return listOfItems.Where(item=>item.CheckStatus); } } public TheViewModel(IList<ISelectable> dependencyItems) { listOfItems= new ObservableCollection<ISelectable>(dependencyItems); } //here is your list... private List<string> selectedNames //use this... private void CollectNamesOfSelectedElements() { selectedNames = new List<string>(); foreach(ISelectable item in GetSelectedElements) { //you should override the ToString in your model if you want to do this... selectedNames.Add(item.ToString()); } } } 

RelayCommand article

View: (Keep here all of your usercontrols.)

In your UserControl (xaml):

<UserControl x:Class="View.ListViewDataStyle" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" mc:Ignorable="d"> <Button Command={Binding SaveCheckedItemsText}/> <!-- Another content --> <ListView ItemsSource="{Binding ListOfItems}"> <ListView.ItemTemplate> <DataTemplate> <CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> </UserControl> 

And with interface here is the xaml.cs code (for UserControls):

public partial class ListViewDatabaseStyle : UserControl, IControlView { public ListViewDatabaseStyle () { InitializeComponent(); } public INotifyPropertyChanged ViewModel { get { return (INotifyPropertyChanged)DataContext; } set { DataContext = value; } } } 

And the last one is the Model project with your models:

 public interface ISelectable { bool CheckStatus { get; set; } } public class CheckBoxListModel : INotifyPropertyChanged, ISelectable { private string text; public string Text { get { return text; } set { text = value; RaiseChanged("Text"); } } private bool checkStatus; public bool CheckStatus { get { return checkStatus; } set { checkStatus = value; RaiseChanged("CheckStatus"); } } private void RaiseChanged(string propName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } public event PropertyChangedEventHandler PropertyChanged; } } 

Excuse me for english grammar mistakes, i hope you understood my post.

Update: Use the DI techn. to avoid the reference to view from viewmodel. DI service will inject the correct object with constructor injection.

15 Comments

Many thanks Peter for your detailed explain. I will try to do the same and I'm sure it will work this time. Thanks again. :)
small question, the ViewService and the singleton you mentioned, where that should be created?
I defined this in the Model Project. Because of dependencies you can use this anywhere in your ViewModel project.
So the "ViewService", the "IControlView" and "ISelectable" are they part of the class library called "Model" file, or separate classes inside the that class library? if you know what I mean
I made another project for interfaces and all projects have access for this interface project. So you can define all of your interfaces in this separated project. In this way IControlView and ISelectable in Interfaces project and ViewService class in Model project. By the way all of project are "class library" type (except the StartUp WPF project).
|
1
<UserControl.DataContext> <TheViewModel:TheViewModelClass/> </UserControl.DataContext> <ListView ItemsSource="{Binding ListOfItems}"> <ListView.ItemTemplate> <DataTemplate> <CheckBox Content="{Binding Text}" IsChecked="{Binding CheckedStatus}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> 

3 Comments

this didn't work, it's same what Peter suggested. It shows like this: TheModel.CheckBoxListModel TheModel.CheckBoxListModel TheModel.CheckBoxListModel
But this is how it should work. The problem is probably not in your xaml, but it the code you haven't shown us. I see that you have manipulated with the code when copy&pasting here, because there are errors in your viewmodel. Nevermind, this is the right way to do it. Check your Output windows when debugging the app. BTW, haven't you modified the ControlTemplate of CheckBox?
yes I have modified it and yes the code is different because i'm working on a copy from it. hopefully with some help I'll be able to understand why it's not working :) thanks all