Databinding and DataContext in WPF Desktop application with multiple views using UserControls - c#

I am working on my first project with WPF and as practice, have created an application that has a menu bar with two buttons on the side which are used to change the display two different views on the grid to its right. I am using the MVVM pattern. Using the ICommand interface, I am able to change the view in the grid using usercontrols for the two views from the menu bar buttons. The application is as follows:
The menu bar has buttons named View 1 and View 2, clicking any of them will open up the relevant view in the usercontrol. At startup a "defaultview" is displayed. The default view has a button which can also be used to move to View 1. Below is what I am stuck at:
I have a button in View 1 to change the display to View 2 (it calls the same command I made in the App View Model but even though it changes the variable value to which the sidegrid is bound too, the view displayed doesnt change).
I want to pass data (tried with First Name) from View 1 to View 2 but I cant get binding to work.
Can someone guide me or link me to examples or material that discuss this?
App View Model
using DataBindingAndViewsTestProject.Views;
using Hotel_Management_Project_WPF.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataBindingAndViewsTestProject.ViewModels
{
public class AppVM : ObservableObject
{
//Create a property that controls current view
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set { OnPropertyChanged(ref _currentView, value);
}
}
//Instantiate the relaycommands, we will need to instantiate relaycommand objects for every command we need to perform.
//This means that we will need to do this for preses of all buttons
public RelayCommand View1ButtonCommand { get; private set; }
public RelayCommand View2ButtonCommand { get; private set; }
public AppVM()
{
CurrentView = new DefaultVM();
View1ButtonCommand = new RelayCommand(ShowView1, AlwaysTrueCommand);
View2ButtonCommand = new RelayCommand(ShowView2, AlwaysTrueCommand);
}
public void ShowView1(object dummy)
{
CurrentView = new View1();
}
public void ShowView2(object dummy)
{
CurrentView = new View2();
}
public bool AlwaysTrueCommand(object dummy)
{
return true;
}
}
}
Main Window.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="600"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button x:Name="view1Button" Content="Go to View 1" Margin="10" Command="{Binding View1ButtonCommand}"></Button>
<Button x:Name="view2Button" Content="Go to View 2" Margin="10" Command="{Binding View2ButtonCommand}"></Button>
</StackPanel>
<Grid Grid.Column="1">
<ContentControl Content="{Binding CurrentView}"></ContentControl>
</Grid>
</Grid>
</Window>
View1.xaml
<UserControl x:Class="DataBindingAndViewsTestProject.Views.View1"
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"
xmlns:local="clr-namespace:DataBindingAndViewsTestProject.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
xmlns:vm="clr-namespace:DataBindingAndViewsTestProject.ViewModels">
<UserControl.Resources>
<vm:AppVM x:Name="AppVMinView1" x:Key="AppVMinView1"></vm:AppVM>
</UserControl.Resources>
<UserControl.DataContext>
<vm:View1VM></vm:View1VM>
</UserControl.DataContext>
<Grid Background="Aqua">
<StackPanel Margin="100">
<TextBlock Text="First Name"/>
<TextBox x:Name="firstNameTextBoxView1" Text="{Binding View1InfoClass.FirstName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Last Name"/>
<TextBox x:Name="lastNameTextBoxView1" Text="{Binding View1InfoClass.LastName, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Random Useless Number" ></TextBlock>
<TextBox x:Name="randomUselessNumberView1" Text="{Binding View1InfoClass.Number, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="First Name Entered"></TextBlock>
<TextBlock Text="{Binding View1InfoClass.FirstName}"></TextBlock>
<TextBlock Text="Last Name Entered" ></TextBlock>
<TextBlock Text="{Binding View1InfoClass.LastName}"></TextBlock>
<TextBlock Text="Random Useless Number Entered"></TextBlock>
<TextBlock Text="{Binding View1InfoClass.Number}"></TextBlock>
<Button DataContext="{StaticResource AppVMinView1}" Content="Go to view2" Height="20" Width="70" Command="{Binding View2ButtonCommand}" />
</StackPanel>
</Grid>
</UserControl>
View2.xaml
<UserControl x:Class="DataBindingAndViewsTestProject.Views.View2"
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"
xmlns:local="clr-namespace:DataBindingAndViewsTestProject.Views"
xmlns:vm="clr-namespace:DataBindingAndViewsTestProject.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<vm:View1VM x:Key="View1VMInView2" x:Name="View1VMInView2"></vm:View1VM>
</UserControl.Resources>
<UserControl.DataContext>
<vm:View2VM></vm:View2VM>
</UserControl.DataContext>
<Grid Background="Beige">
<StackPanel Margin="100">
<TextBlock Text="First Name"/>
<TextBox x:Name="firstNameTextBoxView2" Text="{Binding View2InfoClass.FirstName, Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Last Name"/>
<TextBox x:Name="lastNameTextBoxView2" Text="{Binding View2InfoClass.LastName, Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="Random Useless Number"></TextBlock>
<TextBox x:Name="randomUselessNumberView2" Text="{Binding View2InfoClass.Number, Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged}"></TextBox>
<TextBlock Text="First Name Entered"></TextBlock>
<TextBlock DataContext="View1InView2" Text="{Binding View1InfoClass.FirstName}" ></TextBlock>
<TextBlock Text="Last Name Entered"></TextBlock>
<TextBlock></TextBlock>
<TextBlock Text="Random Useless Number Entered"></TextBlock>
<TextBlock ></TextBlock>
</StackPanel>
</Grid>
</UserControl>
Model (if relevant)
public class InfoClass:ObservableObject
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set {
OnPropertyChanged(ref _firstName, value);
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { OnPropertyChanged(ref _lastName, value); }
}
private int _number;
public int Number
{
get { return _number; }
set { OnPropertyChanged(ref _number, value); }
}
}

Related

In WPF MVVM, i have a usercontrol with a check box, how do i connect a command to the viewmodel

I am still new to WPF and MVVM and am trying to keep the seperation between View and View Model.
i have an app, essentially a projects task list app, in this i create projects and within each project i can create a set of tasks. Most is working well, but essentially i cannot get a command binding on a checkbox in a user control to work using DP, inherited datacontext etc. i always ge a binding failed error when running the app. i am trying to bing to a command in the viewmodel of the view which contains the user controls.
i created a user control to pull the task data together in the view, the command is on the checkbox
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
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"
xmlns:local="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding SetComplete}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
in the user control code behind i have set the DP and the set text function is working
namespace TaskProjectApp.Controls
{
/// <summary>
/// Interaction logic for TaskControl.xaml
/// </summary>
public partial class TaskControl : UserControl
{
public UserTask Task
{
get { return (UserTask)GetValue(TaskProperty); }
set { SetValue(TaskProperty, value); }
}
// Using a DependencyProperty as the backing store for Task. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TaskProperty =
DependencyProperty.Register("Task", typeof(UserTask), typeof(TaskControl), new PropertyMetadata(new UserTask()
{
Title = "title",
Description = "none",
Comments = "none"
}, SetText));
private static void SetText(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TaskControl task = d as TaskControl;
if (task != null)
{
task.titleTB.Text = (e.NewValue as UserTask).Title;
task.DescriptionTB.Text = (e.NewValue as UserTask).Description;
task.priority.Text = (e.NewValue as UserTask).Priority.ToString();
task.iscomplete.IsChecked = (e.NewValue as UserTask).IsComplete;
}
}
public TaskControl()
{
InitializeComponent();
}
}
}
now to make this work i set the binding of the user control in the window as so, the listview takes the usercontrols and implements the observable collection of tasks.
<Window x:Class="TaskProjectApp.Views.ProjectsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TaskProjectApp.Views"
xmlns:uc="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
Title="ProjectsView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<uc:ProjectControl Project="{Binding UserProject}" />
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
</Grid>
</Window>
this seems to completely stop me using the command, i set the datacontext in the code behind, to the whole window
public partial class ProjectsView : Window
{
public ProjectViewModel ProjectViewModel { get; set; }
public ProjectsView()
{
InitializeComponent();
}
public ProjectsView(UserProject userProject)
{
InitializeComponent();
ProjectViewModel = new ProjectViewModel(userProject);
DataContext = ProjectViewModel;
}
}
and reading trying to solve this has shown that the usercontrol should inherit the datacontext of the parent window.
i have seen solutions using relative paths and DPs for the commands as well as people saying these are not needed just let the inherited datacontext handle it.
but i have tried all three an neither works.
the interface shows me a message box saying no datacontext found, although i notice this is the case when you set the datacontext in code behind and not the xaml.
the SetCommand is created in the projects view model and its a property not a field as i have seen this fail for that reason too.
namespace TaskProjectApp.ViewModels
{
public class ProjectViewModel
{
public UserProject UserProject { get; set; }
public ProjectViewModel(UserProject userProject)
{
UserProject = userProject;
Tasks = new ObservableCollection<UserTask>();
NewProjectTask = new NewProjectTaskCommand(this);
DeleteProjectTask = new DeleteProjectTaskCommand(this);
SetComplete = new SetCompleteCommand();
ReadTaskDatabase();
}
public ObservableCollection<UserTask> Tasks { get; set; }
public NewProjectTaskCommand NewProjectTask { get; set; }
public DeleteProjectTaskCommand DeleteProjectTask { get; set; }
public SetCompleteCommand SetComplete { get; set; }
public UserTask SelectedTask { get; set; }
public void ReadTaskDatabase()
{
List<UserTask> list = new List<UserTask>();
using (SQLiteConnection newConnection = new SQLiteConnection(App.databasePath))
{
newConnection.CreateTable<UserTask>();
list = newConnection.Table<UserTask>().ToList().OrderBy(c => c.Title).ToList();
}
Tasks.Clear();
foreach (UserTask ut in list)
{
if (ut.ProjectId == UserProject.Id)
{
Tasks.Add(ut);
}
}
}
}
}
if anyone can point out where i am going wrong tat will be great as i fear i am now not seeing the wood for the trees.
I found the solution thanks to Ash link Binding to Window.DataContext.ViewModelCommand inside a ItemsControl not sure how i missed it, maybe wrong key words. anyway because the datacontext of the usercontrol is being made into my data class in the observable list Tasks
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
you need to use a relative path inside the user control to look up past the ItemTemplate to the ListView itself as this uses the viewmodel data context to bind to, so has access to the right level
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
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"
xmlns:local="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding DataContext.SetComplete, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
this might be limiting in future as it measn the usercontrol will look for a listview to bind the command, but it solves the immediate issue.

Closing an Open Window Using MVVM Pattern, Produces System.NullReferenceException error

I'm trying to learn MVVM pattern using WPF C#. And I'm running into an error when trying to close an opened window after saving information to an sqlite database. When the command to save a new contact is raised, I am getting an error on HasAddedContact(this, new EventArgs());
Error: System.NullReferenceException: 'Object reference not set to an instance of an object.'
My ViewModel:
public class NewContactViewModel : BaseViewModel
{
private ContactViewModel _contact;
public ContactViewModel Contact
{
get { return _contact; }
set { SetValue(ref _contact, value); }
}
public SaveNewContactCommand SaveNewContactCommand { get; set; }
public event EventHandler HasAddedContact;
public NewContactViewModel()
{
SaveNewContactCommand = new SaveNewContactCommand(this);
_contact = new ContactViewModel();
}
public void SaveNewContact()
{
var newContact = new Contact()
{
Name = Contact.Name,
Email = Contact.Email,
Phone = Contact.Phone
};
DatabaseConnection.Insert(newContact);
HasAddedContact(this, new EventArgs());
}
}
SaveNewContactCommand:
public class SaveNewContactCommand : ICommand
{
public NewContactViewModel VM { get; set; }
public SaveNewContactCommand(NewContactViewModel vm)
{
VM = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
VM.SaveNewContact();
}
}
NewContactWindow.Xaml.Cs code behind:
public partial class NewContactWindow : Window
{
NewContactViewModel _viewModel;
public NewContactWindow()
{
InitializeComponent();
_viewModel = new NewContactViewModel();
DataContext = _viewModel;
_viewModel.HasAddedContact += Vm_ContactAdded;
}
private void Vm_ContactAdded(object sender, EventArgs e)
{
this.Close();
}
}
Adding additional code where I call the new window:
public class ContactsViewModel
{
public ObservableCollection<IContact> Contacts { get; set; } = new ObservableCollection<IContact>();
public NewContactCommand NewContactCommand { get; set; }
public ContactsViewModel()
{
NewContactCommand = new NewContactCommand(this);
GetContacts();
}
public void GetContacts()
{
using(var conn = new SQLite.SQLiteConnection(DatabaseConnection.dbFile))
{
conn.CreateTable<Contact>();
var contacts = conn.Table<Contact>().ToList();
Contacts.Clear();
foreach (var contact in contacts)
{
Contacts.Add(contact);
}
}
}
public void CreateNewContact()
{
var newContactWindow = new NewContactWindow();
newContactWindow.ShowDialog();
GetContacts();
}
}
ContactsWindow.Xaml
<Window x:Class="Contacts_App.View.ContactsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="Contacts Window" Height="320" Width="400">
<Window.Resources>
<vm:ContactsViewModel x:Key="vm"/>
</Window.Resources>
<StackPanel Margin="10">
<Button
Content="New Contact"
Command="{Binding NewContactCommand}"/>
<TextBox Margin="0,5,0,5"/>
<ListView
Height="200"
Margin="0,5,0,0"
ItemsSource="{Binding Contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
NewContactWindow.Xaml
<Window x:Class="Contacts_App.View.NewContactWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Window.Resources>
<vm:NewContactViewModel x:Key="vm"/>
</Window.Resources>
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Source={StaticResource vm}, Path=Contact.Phone, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding Source={StaticResource vm}, Path=SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>
You're creating NewContactWindow's viewmodel in the constructor, correctly assigning it to DataContext, and correctly adding a handler to that event. Unfortunately, you also create a second instance of the same viewmodel in resources, and you manually set the Source property of all the bindings to use the one in the resources, which doesn't have the event handler.
Window.DataContext, which you set in the constructor, is the default Source for any binding in the Window XAML. Just let it do its thing. I also removed all the redundant Mode=TwoWay things from the Bindings to TextBox.Text, since that property is defined so that all bindings on it will be TwoWay by default. I don't think UpdateSourceTrigger=PropertyChanged is doing anything necessary or helpful either: That causes the Binding to update your viewmodel property every time a key is pressed, instead of just when the TextBox loses focus. But I don't think you're doing anything with the properties where that would matter; there's no validation or anything. But TextBox.Text is one of the very few places where that's actually used, so I left it in.
You should remove the analagous viewmodel resource in your other window. It's not doing any harm, but it's useless at best. At worst, it's an attractive nuisance. Kill it with fire and bury the ashes under a lonely crossroads at midnight.
<Window x:Class="Contacts_App.View.NewContactWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Contacts_App.View"
xmlns:vm="clr-namespace:Contacts_App.ViewModel"
mc:Ignorable="d"
Title="New Contact Window" Height="250" Width="350">
<Grid>
<StackPanel
Margin="10">
<Label Content="Name" />
<TextBox
Text="{Binding Contact.Name, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Email" />
<TextBox
Text="{Binding Contact.Email, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Label Content="Phone Number" />
<TextBox
Text="{Binding Contact.Phone, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,5"/>
<Button
Content="Save"
Command="{Binding SaveNewContactCommand}"/>
</StackPanel>
</Grid>
</Window>

Filling a ListBox with an ObservableCollection and display properties of selected items in StackPanel

I'm tring to databind properties of an ObservableCollection to a ListBox (Just the Title property for example).
By clicking on one of the ListItem (with an event ), i'd like to display all the properties of the Collection into a StackPanel. After many tries, I still don't know how can I figure it out...
Here is my code behind :
public partial class TestListView : Window
{
public TestListView()
{
ObservableCollection<Programme> pgr = new ObservableCollection<Programme>();
pgr = readfile();
InitializeComponent();
}
public class Programme
{
public String Title { get; set; }
public String Date { get; set; }
public String Chaine { get; set; }
public Programme(String Title, String Date, String Chaine)
{
this.Title = Title;
this.Date = Date;
this.Chaine = Chaine;
}
}
Here is my XAML :
<Window x:Class="Test.TestListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test;assembly=Test"
Title="TestListView" Height="500" Width="1000" x:Name="Window">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="249*"/>
<ColumnDefinition Width="743*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20*"/>
<RowDefinition Height="428*"/>
<RowDefinition Height="21*"/>
</Grid.RowDefinitions>
<ListBox Name="l1" ItemsSource="{Binding pgr}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Grid.Row="1">
<TextBox Margin="343,0,0,0" x:Name="Recherche"></TextBox>
<Button Height="37" Margin="669,0,0,0" ></Button>
<TextBlock x:Name="t1" Margin="214,0,293,0" Height="33" />
</StackPanel>
</Grid>
</Window>
You need to bind your data to (public) properties. Also, you don't need to use ObservableCollection; any selection changes will be picked up anyhow.
Here's a working sample, with layout and other bits and pieces changed to make it compile for me:
public partial class MainWindow
{
public IList<Programme> pgr { get; }
public MainWindow()
{
pgr = new List<Programme>
{
new Programme("First", "FirstDate", "FirstChaine"),
new Programme("Second", "SecondDate", "SecondChaine"),
new Programme("Third", "ThirdDate", "ThirdChaine"),
};
InitializeComponent();
}
public class Programme
{
// No changes
}
}
...and the XAML:
<Window
x:Name="self"
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<StackPanel DataContext="{Binding ElementName=self}" Orientation="Horizontal">
<ListBox Name="l1" ItemsSource="{Binding pgr}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Vertical">
<TextBox Margin="10" Text="{Binding ElementName=l1,Path=SelectedItem.Title}" />
<Button Height="37" Margin="0" Content="{Binding ElementName=l1,Path=SelectedItem.Date}"></Button>
<TextBlock Margin="10" Height="33" Text="{Binding ElementName=l1,Path=SelectedItem.Chaine}" />
</StackPanel>
</StackPanel>
</Window>
Note the bindings that reference the selected item:
Text="{Binding ElementName=l1,Path=SelectedItem.Title}"

Property update withing of MVVM

I've created an example based on MVVM
Main window XAML:
<Window x:Class="LearnMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LearnMVVM"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="operationTypeEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:OperationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<DataTemplate DataType="{x:Type local:SomeUserControlViewModel}">
<local:SomeUserControl />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="25"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" Margin="2" Text="{Binding Path=A, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="+" TextAlignment="Center" VerticalAlignment="Center" Height="16" Margin="0,4,0,5"/>
<TextBox Grid.Column="2" Grid.Row="0" Margin="2" Text="{Binding Path=B, Mode=TwoWay}"/>
<Button Grid.Column="3" Grid.Row="0" Margin="2" Content="Посчитать" Command="{Binding ClickCommand}"/>
<TextBox Grid.Column="4" Grid.Row="0" Margin="2" IsReadOnly="True" Text="{Binding Path=Summa, Mode=TwoWay}"/>
<ComboBox Grid.Column="2" Grid.Row="1" SelectedItem="{Binding Path=SomeUserControl.Operation, Mode=TwoWay}" ItemsSource="{Binding Source={StaticResource operationTypeEnum}}" />
<ContentControl Grid.Column="2" Grid.Row="2" BorderThickness="3" BorderBrush="Black" Content="{Binding Path=SomeUserControl}" />
</Grid>
</Window>
XAML of the SomeUserControl:
<UserControl x:Class="LearnMVVM.SomeUserControl"
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"
xmlns:learnMvvm="clr-namespace:LearnMVVM"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<learnMvvm:SomeUserControlViewModel />
</UserControl.DataContext>
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=A, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Content="{Binding Path=Operation, diag:PresentationTraceSources.TraceLevel=High}" />
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=B, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Button DockPanel.Dock="Top" Content="=" Margin="20" Command="{Binding CalculateOperationComamnd, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Margin="10" Content="{Binding Path=Result, diag:PresentationTraceSources.TraceLevel=High}" />
</DockPanel>
</UserControl>
ViewModel of the SomeCustomUserControl:
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace LearnMVVM
{
public enum OperationType
{
Sum,
Sub,
Div,
Mul
}
public class SomeUserControlViewModel : INotifyPropertyChanged
{
public double A { get; set; }
public double B { get; set; }
//Команды
private ICommand calculateOperationCommand;
public ICommand CalculateOperationComamnd
{
get
{
return calculateOperationCommand;
}
set
{
if (calculateOperationCommand != value)
{
calculateOperationCommand = value;
OnPropertyChanged("CalculateOperationComamnd");
}
}
}
private OperationType operation;
public OperationType Operation
{
get
{
return operation;
}
set
{
if (operation != value)
{
operation = value;
switch (operation)
{
case OperationType.Sum:
CalculateOperationComamnd = new RelayCommand(arg => OperationSum());
break;
case OperationType.Sub:
CalculateOperationComamnd = new RelayCommand(arg => OperationSub());
break;
case OperationType.Div:
CalculateOperationComamnd = new RelayCommand(arg => OperationDiv());
break;
case OperationType.Mul:
CalculateOperationComamnd = new RelayCommand(arg => OperationMul());
break;
}
OnPropertyChanged("Operation");
}
}
}
private void OperationSum()
{
Result = A + B;
}
private void OperationSub()
{
Result = A - B;
}
private void OperationDiv()
{
Result = A/B;
}
private void OperationMul()
{
Result = A*B;
}
private double result;
public double Result
{
get { return result; }
set
{
if (Math.Abs(result - value) > 0.0001)
{
result = value;
OnPropertyChanged("Result");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Problem: the custom control does not change, when i'm changing selected item from the combo box and the "calculate button has no effect.
Actually all properties within of SomeCustomControlViewModel are updated as expected, but there is no effect in the main windows.
Have i missed something?
Operation is not a property of SomeUserControl. It is a property of SomeUserControl's viewmodel -- reachable as the control's DataContext. Try binding ComboBox.SelectedItem like so:
SelectedItem="{Binding Path=SomeUserControl.DataContext.Operation, Mode=TwoWay}"
The change is that I added DataContext to the path.
This is why you don't use viewmodels with custom controls, if you really want to use them as controls. You write a control class deriving from Control, and give it dependency properties. Operation should be a dependency property of a class derived from Control, not a notifying property on a viewmodel. Then you define UI for it by applying a ControlTemplate via a default Style.
What you've got here is really a child viewmodel. With that type of arrangement, ordinarily the parent viewmodel would provide an instance of the child viewmodel, and bind it to the child control itself. Then anybody who wanted to use a property of the child viewmodel would bind ChildVM.WhateverProperty.

Prism 6 Navigation Wierdness

I am still learning prism and I want to understand navigation. Having read the manual and followed #brianlagunas MVVM made simple webinar online, I decided to try a small project and see. I get a stackoverflow exception when I try to navigate to a view that has a viewmodel data-bound to some controls on my view. Here is the code below:
Profile View with empty viewmodel:
<UserControl x:Class="SampleLogin.Views.Profile"
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"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:SampleLogin.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock x:Name="ProfileTextBlock" HorizontalAlignment="Center" Margin="74,29,78,0" TextWrapping="Wrap" Text="PROFILE PAGE" VerticalAlignment="Top" Height="45" Width="148" FontSize="21.333" FontWeight="Bold"/>
<TextBlock x:Name="Username" HorizontalAlignment="Left" Height="26" Margin="103,96,0,0" TextWrapping="Wrap" Text="{Binding username}" VerticalAlignment="Top" Width="130"/>
<TextBlock x:Name="LoginTime" HorizontalAlignment="Left" Margin="109,152,0,0" TextWrapping="Wrap" Text="{Binding LoginTime}" VerticalAlignment="Top" Width="124" Height="33"/>
<Label x:Name="label" Content="Username :" HorizontalAlignment="Left" Margin="10,96,0,0" VerticalAlignment="Top" Width="71" FontWeight="Bold"/>
<Label x:Name="label1" Content="Login-Time:" HorizontalAlignment="Left" Height="26" Margin="12,159,0,0" VerticalAlignment="Top" Width="86" FontWeight="Bold"/>
</Grid>
but it has an empty viewmodel and it loads fine.
Here is another view with a simple form that is data-bound to its viewmodel
<UserControl x:Class="SampleLogin.Views.LoginUser"
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"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:SampleLogin.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="325">
<Grid>
<Label x:Name="usernameLabel" Content="Username :" HorizontalAlignment="Left" Margin="10,93,0,0" VerticalAlignment="Top" Width="113" Height="35" FontSize="18.667" FontWeight="Bold"/>
<Label x:Name="label" Content="Password :
" HorizontalAlignment="Left" Margin="10,146,0,0" VerticalAlignment="Top" Width="100" Height="38" FontSize="18.667" FontWeight="Bold"/>
<TextBox x:Name="Username" HorizontalAlignment="Left" Height="35" Margin="132,93,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="178" Text="{Binding Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<PasswordBox x:Name="passwordBox" HorizontalAlignment="Left" Margin="132,146,0,0" VerticalAlignment="Top" Width="178" Height="35"/>
<Button x:Name="Loginbtn" Content="Login" HorizontalAlignment="Left" Margin="85,208,0,0" VerticalAlignment="Top" Width="89" Height="42" FontSize="18.667" FontWeight="Bold"
Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=passwordBox}" />
<Button x:Name="Logoutbtn" Content="Logout" HorizontalAlignment="Left" Margin="230,208,0,0" VerticalAlignment="Top" Width="80" Height="42" FontWeight="Bold" FontSize="18.667"/>
</Grid>
Here is the viewmodel its bound to: left it very simple just to understand the concept:
public class LoginUserViewModel : BindableBase
{
private AuthenticationService _auth;
public DelegateCommand<object> LoginCommand { get; set; }
public LoginUserViewModel(AuthenticationService auth)
{
_auth = auth;
LoginCommand = new DelegateCommand<object>(LoginUser, CanLoginUser);
}
private void LoginUser(object obj)
{
var passwdbox = obj as PasswordBox;
_auth.LoginUser(Username, passwdbox.SecurePassword);
}
private bool CanLoginUser(object obj)
{
var password = obj as PasswordBox;
return string.IsNullOrWhiteSpace(Username);
}
private string _username = "Enter Username here";
public string Username
{
get { return _username; }
set { SetProperty(ref _username, value); }
}
}
And here is the MainWindowViewmodel that handles the navigation using the button at the top of my mainwindow view.
public class MainWindowViewModel : BindableBase
{
IRegionManager _regions;
public DelegateCommand<string> NavigationCommand { get; set; }
public MainWindowViewModel(IRegionManager regions)
{
_regions = regions;
NavigationCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string uri)
{
_regions.RequestNavigate("ContentRegion", uri);
}
}
And lastly, here is the MainWindowViewModel that binds to the viewmodel:
<Window x:Class="SampleLogin.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:SampleLogin.Views"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Login" Margin="5" Command="{Binding NavigationCommand, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" CommandParameter="LoginUser" />
<Button Content="Home" Margin="5" Command="{Binding NavigationCommand, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" CommandParameter="Home"/>
<Button Content="Profile" Margin="5" Command="{Binding NavigationCommand}" CommandParameter="Profile"/>
</StackPanel>
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
</Grid>
I find this very strange. The first view works when I click the buttons at the top of the screen for navigation as long as I leave the ViewModel blank. Same with the view that has the form, if I comment out the ViewModel, it navigates with no issues, showing the form and all. But if the databinding is there in the viewmodel I get the exception. Any ideas?! I am stomped and not sure where my mistake is.
Edit:
Having thought about this problem and tried to move it forward, I discovered that the error comes from the fact that my LoginUserViewModel having constructor parameters is why I am having the errors, tried using unity container in the bootstrapper register LoginUserViewmodel's dependencies but I am still getting the errors, any ideas anyone?
After doing some more searching and question asking, I got the answer to this problem. I spoke to the awesome #brianLagunas and shared my code with him and he made understand the problem I had. Its in a domain model which I didn't include in my post.
public class User : IUser
{
public User()
{
}
public User(User user){} //this is the problem line
[Required]
[Key]
public int UserId { get; set; }
[Required(ErrorMessage ="You must supply a Username")]
[DataType(DataType.Text)]
public string Username { get; private set; }
[Required(ErrorMessage = "You must enter a valid password to Login!")]
public SecureString Password { get; private set; }
[DataType(DataType.Time)]
[Required]
public TimeSpan TimeLoggedIn { get; private set; }
[DataType(DataType.Date)]
public DateTime LoginHistory { get; private set; }
public void SetUserDetails(string username, TimeSpan date, DateTime History)
{
if(string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException("You cannot have any of the Login Fields empty..!");
}
Username = username;
TimeLoggedIn = date;
LoginHistory = History;
}
}
The second constructor was casing an infinite loop this the stack overflow error I was getting. Removing that constructor fixed the issue and all is well.

Categories