Nested Data Binding using MVVM in WPF not working - c#

I am not able to figure out why my third Nested DataBinding in WPF is not working. I am using Entity Framework and Sql Server 2012 and following are my entities. An Application can have more than one accounts. There is an Accounts Table and an Applications Table.
ENTITIES
1. Applications
2. Accounts
VIEWMODELS
1. ApplicationListViewModel
2. ApplicationViewModel
3. AccountListViewModel
4. AccountViewModel
In my usercontrol I am trying to do following:
1. Use combobox to select an application using ApplicationListViewModel (Working)
2. Upon selected application display all accounts in datagrid (Working)
3. Upon selected account display details information about a particular account.(Does not show details of the selected account)
<UserControl.Resources>
<vm:ApplicationListViewModel x:Key="AppList" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource AppList}}">
<Grid>
<Grid.RowDefinitions>
...
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0">
<GroupBox Header="View all">
<StackPanel>
<!-- All Applications List -->
<ComboBox x:Name="cbxApplicationList"
ItemsSource="{Binding Path=ApplicationList}"
DisplayMemberPath="Title" SelectedValuePath="Id"
SelectedItem="{Binding Path=SelectedApplication, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" />
<!-- Selected Application Accounts -->
<DataGrid x:Name="dtgAccounts" Height="Auto" Width="auto" AutoGenerateColumns="False"
DataContext="{Binding SelectedApplication.AccountLVM}"
ItemsSource="{Binding Path=AccountList}"
SelectedItem="{Binding SelectedAccount, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Binding="{Binding Path=Title}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</GroupBox>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" >
<GroupBox x:Name="grpBoxAccountDetails" Header="New Account" >
<!-- Selected Account Details -->
<!-- DataContext binding does not appear to work -->
<StackPanel DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}" >
<Grid>
<Grid.RowDefinitions>
...
</Grid.ColumnDefinitions>
<TextBlock x:Name="lblApplication" Grid.Row="0" Grid.Column="0" >Application</TextBlock>
<ComboBox x:Name="cbxApplication" Grid.Row="0" Grid.Column="1"
DataContext="{Binding Source={StaticResource AppList}}"
ItemsSource="{Binding ApplicationList}"
DisplayMemberPath="Title" SelectedValuePath="Id"
SelectedValue="{Binding SelectedApplication.AccountLVM.SelectedAccount.ApplicationId}">
</ComboBox>
<TextBlock x:Name="lblTitle" Grid.Row="0" Grid.Column="0" >Title</TextBlock>
<TextBox x:Name="txtTitle" Grid.Row="0" Grid.Column="1" Height="30" Width="200"
Text="{Binding Title}" DataContext="{Binding Mode=OneWay}"></TextBox>
<Button Grid.Row="1" Grid.Column="0" Command="{Binding AddAccount}">Add</Button>
</Grid>
</StackPanel>
</GroupBox>
</StackPanel>
</Grid>
</StackPanel>
ApplicationListViewModel
class ApplicationListViewModel : ViewModelBase
{
myEntities context = new myEntities();
private static ApplicationListViewModel instance = null;
private ObservableCollection<ApplicationViewModel> _ApplicationList = null;
public ObservableCollection<ApplicationViewModel> ApplicationList
{
get
{
return GetApplications();
}
set {
_ApplicationList = value;
OnPropertyChanged("ApplicationList");
}
}
//public ObservableCollection<ApplicationViewModel> Cu
private ApplicationViewModel selectedApplication = null;
public ApplicationViewModel SelectedApplication
{
get
{
return selectedApplication;
}
set
{
selectedApplication = value;
OnPropertyChanged("SelectedApplication");
}
}
//private ICommand showAddCommand;
public ApplicationListViewModel()
{
this._ApplicationList = GetApplications();
}
internal ObservableCollection<ApplicationViewModel> GetApplications()
{
if (_ApplicationList == null)
_ApplicationList = new ObservableCollection<ApplicationViewModel>();
_ApplicationList.Clear();
foreach (Application item in context.Applications)
{
ApplicationViewModel a = new ApplicationViewModel(item);
_ApplicationList.Add(a);
}
return _ApplicationList;
}
public static ApplicationListViewModel Instance()
{
if (instance == null)
instance = new ApplicationListViewModel();
return instance;
}
}
ApplicationViewModel
class ApplicationViewModel : ViewModelBase
{
private myEntities context = new myEntities();
private ApplicationViewModel originalValue;
public ApplicationViewModel()
{
}
public ApplicationViewModel(Application acc)
{
//Initialize property values
this.originalValue = (ApplicationViewModel)this.MemberwiseClone();
}
public ApplicationListViewModel Container
{
get { return ApplicationListViewModel.Instance(); }
}
private AccountListViewModel _AccountLVM = null;
public AccountListViewModel AccountLVM
{
get
{
return GetAccounts();
}
set
{
_AccountLVM = value;
OnPropertyChanged("AccountLVM");
}
}
internal AccountListViewModel GetAccounts()
{
_AccountLVM = new AccountListViewModel();
_AccountLVM.AccountList.Clear();
foreach (Account i in context.Accounts.Where(x=> x.ApplicationId == this.Id))
{
AccountViewModel account = new AccountViewModel(i);
account.Application = this;
_AccountLVM.AccountList.Add(account);
}
return _AccountLVM;
}
}
AccountListViewModel
class AccountListViewModel : ViewModelBase
{
myEntities context = new myEntities();
private static AccountListViewModel instance = null;
private ObservableCollection<AccountViewModel> _accountList = null;
public ObservableCollection<AccountViewModel> AccountList
{
get
{
if (_accountList != null)
return _accountList;
else
return GetAccounts();
}
set {
_accountList = value;
OnPropertyChanged("AccountList");
}
}
private AccountViewModel selectedAccount = null;
public AccountViewModel SelectedAccount
{
get
{
return selectedAccount;
}
set
{
selectedAccount = value;
OnPropertyChanged("SelectedAccount");
}
}
public AccountListViewModel()
{
this._accountList = GetAccounts();
}
internal ObservableCollection<AccountViewModel> GetAccounts()
{
if (_accountList == null)
_accountList = new ObservableCollection<AccountViewModel>();
_accountList.Clear();
foreach (Account item in context.Accounts)
{
AccountViewModel a = new AccountViewModel(item);
_accountList.Add(a);
}
return _accountList;
}
public static AccountListViewModel Instance()
{
if (instance == null)
instance = new AccountListViewModel();
return instance;
}
}
AccountViewModel. I am eliminating all other initialization logic aside in viewmodel for simplicity.
class AccountViewModel : ViewModelBase
{
private myEntites context = new myEntities();
private AccountViewModel originalValue;
public AccountViewModel()
{
}
public AccountViewModel(Account acc)
{
//Assign property values.
this.originalValue = (AccountViewModel)this.MemberwiseClone();
}
public AccountListViewModel Container
{
get { return AccountListViewModel.Instance(); }
}
public ApplicationViewModel Application
{
get;
set;
}
}
Edit1:
When I data bind to view the details of the SelectedAccount with textbox it doesn't show any text.
1. Able to databind to ApplicationListViewModel to Combobox.
2. Successfully Bind to view AccountList based upon SelectedApplication
3. Unable to Bind to SelectedAcount in the AccountListViewModel.
I think in the following line it doesn't show any details about the selected account. I have checked all databinding syntax. In the properties I am able to view appropriate DataContext and bind to the properties. But it doesn't show any text. When I select each individual record in the DataGrid I am able to debug the call and select the object but somehow that object is not being shown in the textbox at the very end.
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"
Edit2:
Based upon the suggestion in the comment below I tried snoop and was able to see the title textbox row highlighted in red color. I am trying to change the binding Path property and datacontext but still not working. When I tried to click on the "Delve Binding Expression" it gave me unhandled exception. I don't know what that means if as it came from Snoop.
Edit3:
I have taken screenshots of DataContext Property for the StackPanel for the Account Details section and the text property of the textbox.
Solution:
Based upon suggestions below I have made following changes to my solution and made it way more simple. I made it unnecessarily complex.
1. AccountsViewModel
2. AccountViewModel
3. ApplicationViewModel
Now I have created properties as SelectedApplication, SelectedAccount all in just one AccountsViewModel. Removed all complex DataContext syntax and now there is just one DataContext in the xaml page.
Simplified code.
class AccountsViewModel: ViewModelBase
{
myEntities context = new myEntities();
private ObservableCollection<ApplicationViewModel> _ApplicationList = null;
public ObservableCollection<ApplicationViewModel> ApplicationList
{
get
{
if (_ApplicationList == null)
{
GetApplications();
}
return _ApplicationList;
}
set
{
_ApplicationList = value;
OnPropertyChanged("ApplicationList");
}
}
internal ObservableCollection<ApplicationViewModel> GetApplications()
{
if (_ApplicationList == null)
_ApplicationList = new ObservableCollection<ApplicationViewModel>();
else
_ApplicationList.Clear();
foreach (Application item in context.Applications)
{
ApplicationViewModel a = new ApplicationViewModel(item);
_ApplicationList.Add(a);
}
return _ApplicationList;
}
//Selected Application Property
private ApplicationViewModel selectedApplication = null;
public ApplicationViewModel SelectedApplication
{
get
{
return selectedApplication;
}
set
{
selectedApplication = value;
this.GetAccounts();
OnPropertyChanged("SelectedApplication");
}
}
private ObservableCollection<AccountViewModel> _accountList = null;
public ObservableCollection<AccountViewModel> AccountList
{
get
{
if (_accountList == null)
GetAccounts();
return _accountList;
}
set
{
_accountList = value;
OnPropertyChanged("AccountList");
}
}
//public ObservableCollection<AccountViewModel> Cu
private AccountViewModel selectedAccount = null;
public AccountViewModel SelectedAccount
{
get
{
return selectedAccount;
}
set
{
selectedAccount = value;
OnPropertyChanged("SelectedAccount");
}
}
internal ObservableCollection<AccountViewModel> GetAccounts()
{
if (_accountList == null)
_accountList = new ObservableCollection<AccountViewModel>();
else
_accountList.Clear();
foreach (Account item in context.Accounts.Where(x => x.ApplicationId == this.SelectedApplication.Id))
{
AccountViewModel a = new AccountViewModel(item);
_accountList.Add(a);
}
return _accountList;
}
}
XAML Side
<UserControl.Resources>
<vm:AccountsViewModel x:Key="ALVModel" />
</UserControl.Resources>
<StackPanel DataContext="{Binding Source={StaticResource ALVModel}}" Margin="0,0,-390,-29">
<StackPanel>
<ComboBox x:Name="cbxApplicationList"
ItemsSource="{Binding Path=ApplicationList}"
DisplayMemberPath="Title" SelectedValuePath="Id"
SelectedItem="{Binding Path=SelectedApplication, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True"></ComboBox>
<DataGrid x:Name="dtgAccounts" Height="Auto" Width="auto"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=AccountList}"
SelectedItem="{Binding SelectedAccount, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Binding="{Binding Path=Title}"></DataGridTextColumn>
<DataGridTextColumn Header="CreatedDate" Binding="{Binding Path=CreatedDate}"></DataGridTextColumn>
<DataGridTextColumn Header="LastModified" Binding="{Binding Path=LastModifiedDate}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Height="Auto" Width="300" HorizontalAlignment="Left" DataContext="{Binding Path=SelectedAccount}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="200"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="lblTitle" Grid.Row="0" Grid.Column="0" >Title</TextBlock>
<TextBox x:Name="txtTitle" Grid.Row="0" Grid.Column="1" Height="30" Width="200"
Text="{Binding Title}"></TextBox>
</Grid>
</StackPanel>
</StackPanel>
I didn't understood MVVM concept properly. I tried to build everything modular and in the end I screwed it up.

I suspect your problem is related to the fact you are returning a new ObservableCollection every time you call the setter for AccountLVM, and you are not raising your PropertyChange notification, so any existing bindings do not get updated
public AccountListViewModel AccountLVM
{
get
{
return GetAccounts();
}
set
{
_AccountLVM = value;
OnPropertyChanged("AccountLVM");
}
}
internal AccountListViewModel GetAccounts()
{
_AccountLVM = new AccountListViewModel();
_AccountLVM.AccountList.Clear();
foreach (Account i in context.Accounts.Where(x=> x.ApplicationId == this.Id))
{
AccountViewModel account = new AccountViewModel(i);
account.Application = this;
_AccountLVM.AccountList.Add(account);
}
return _AccountLVM;
}
I find your bindings very confusing and hard to follow, however I think whenever this gets evaluated
DataContext="{Binding SelectedApplication.AccountLVM.SelectedAccount}"
it is creating a new AccountLVM, which does not have the SelectedAccount property set.
You don't see the existing DataGrid.SelectedItem change at all because it's still bound to the old AccountLVM as no PropertyChange notification got raised when _accountLVM changed, so the binding doesn't know to update.
But some other miscellaneous related to your code:
Don't change the private version of the property unless you also raise the PropertyChange notification for the public version of the property. This applies to both your constructors and your GetXxxxx() methods like GetAccounts().
Don't return a method call from your getter. Instead set the value using your method call if it's null, and return the private property afterwards.
public AccountListViewModel AccountLVM
{
get
{
if (_accountLVM == null)
GetAccounts(); // or _accountLVM = GetAccountLVM();
return _accountLVM;
}
set { ... }
}
It's really confusing to have the DataContext set in so many controls. The DataContext is the data layer behind your UI, and it's easiest if your UI simply reflects the data layer, and having to go all over the place to get your data makes the data layer really hard to follow.
If you must make a binding to something other than the current data context, try to use other binding properties to specify a different binding Source before immediately going to change the DataContext. Here's an example using the ElementName property to set the binding source:
<TextBox x:Name="txtTitle" ...
Text="{Binding ElementName=dtgAccounts, Path=SelectedItem.Title}" />
The DataContext in inherited, so you don't ever need to write DataContext="{Binding }"
You may want to consider re-writing your parent ViewModel so you can setup XAML like this, without all the extra DataContext bindings or 3-part nested properties.
<ComboBox ItemsSource="{Binding ApplicationList}"
SelectedItem="{Binding SelectedApplication}" />
<DataGrid ItemsSource="{Binding SelectedApplication.Accounts}"
SelectedItem="{Binding SelectedAccount}" />
<StackPanel DataContext="{Binding SelectedAccount}">
...
</StackPanel>
If you're new to the DataContext or struggling to understand it, I'd recommend reading this article on my blog to get a better understanding of what it is and how it works.

Well one major problem with this Binding method is, that the value is only updated, when the last property value, in your case SelectedAccount, is changed. The other levels are not watched by the BindingExpression, so if e.g. SelectedApplication.AccountLVM is changed the DataContext will not notice a difference in SelectedAccount because the binding is still 'watching' on the old reference and you're modifying another reference in your VM.
So I think at the start of the application SelectedApplication is null and the Binding of the ComboBox doesn't notice that it changes. Hmm, I thought about another binding solution, but I couldn't found one. So I suggest, that you create an additional property for reflecting SelectedAccount in your ApplicationListViewModel class.

Related

Returning bound checkbox values using MVVM in a WPF form

I have an object that consists of a string and an array. The string populates a ComboBox and the array populates a ListView depending on the selected string value. Each line of the ListViewconsists of a TextBlock and a CheckBox.
On submit I want to be able to verify which items have been selected for further processing but there's a disconnect when using the MVVM approach. I currently have the DataContext of the submit Button binding to the ListView but only the first value is being returned upon submit (somewhere I need to save the selected values to a list I assume but I'm not sure where). I added an IsSelected property to the model which I think is the first step, but after that I've been grasping at straws.
Model
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
}
ViewModel
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set
{
_selectedId = value;
RaisePropertyChanged(nameof(SelectedId));
}
}
private string _selectedCheckBox;
public string IsSelected
{
get => _selectedCheckBox;
set
{
_selectedCheckBox = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }, IsSelected = false });
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789", "101112" }, IsSelected = false });
Examples = examples;
}
/* BELOW IS A SNIPPET I ADDED FROM AN EXAMPLE I FOUND ONLINE BUT NOT SURE IF IT'S NEEDED */
private ObservableCollection<Example> _bindCheckBox;
public ObservableCollection<Example> BindingCheckBox
{
get => _bindCheckBox;
set
{
_bindCheckBox = value;
RaisePropertyChanged("BindingCheckBox");
}
}
}
}
View
<UserControl x:Class = "DataBinding_WPF.Views.StudentView"
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:DataBinding_WPF"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.IDs}"
DataContext="{Binding DataContext, ElementName=submit_btn}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left" Height="20" Width="100"
Click="Submit" x:Name="submit_btn">Submit</Button>
</StackPanel>
</Grid>
</UserControl>
View.cs
namespace DataBinding_WPF.Views
{
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl
{
public StudentView()
{
InitializeComponent();
}
private void Submit(object sender, EventArgs e)
{
var selectedItems = ((Button)sender).DataContext;
// process each selected item
// foreach (var selected in ....) { }
}
}
}
The ListView control already exposes a selected items collection as property SelectedItems.
private void Submit(object sender, RoutedEventArgs e)
{
var selectedIds = myListView.SelectedItems.Cast<string>().ToList();
// ...do something with the items.
}
However, I doubt that you want to do this in the code-behind, but rather in the view model. For this purpose, WPF offers the concept of commands.
MVVM - Commands, RelayCommands and EventToCommand
What you need is a relay command or delegate command (the name varies across frameworks). It encapsulates a method that should be executed for e.g. a button click and a method to determine whether the command can be executed as an object that can be bound in the view. Unfortunately, WPF does not provide an implementation out-of-the-box, so you either have to copy an implementation like here or use an MVVM framework that already provides one, e.g. Microsoft MVVM Tookit.
You would expose a property Submit of type ICommand in your ExampleViewModel and initialize it in the constructor with an instance of RelayCommand<T> that delegates to a method to execute.
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
public RelayCommand<IList> Submit { get; }
// ...other code.
private void ExecuteSubmit(IList selectedItems)
{
// ...do something with the items.
var selectedIds = selectedItems.Cast<string>().ToList();
return;
}
}
In your view, you would remove the Click event handler and bind the Submit property to the Command property of the Button. You can also bind the SelectedItems property of the ListView to the CommandParameter property, so the selected items are passed to the command on execution.
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
x:Name="submit_btn"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
Additionally, a few remarks about your XAML.
Names of controls in XAML should be Pascal-Case, starting with a capital letter.
You should remove the DataContext binding from ListView completely, as it automatically receives the same data context as the Button anyway.
DataContext="{Binding DataContext, ElementName=submit_btn}"
You can save yourself from exposing and binding the SelectedItem property in your ExampleViewModel, by using Master/Detail pattern for hierarchical data.
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<ListView ItemsSource="{Binding Examples/IDs}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="myCheckBox"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
</StackPanel>
</Grid>
If the view's data context is bound to the view then remove the DataContext from the ListView.
You could remove the item template and instead use a GridView like:
<ListView.View>
<GridView >
<GridViewColumn Header="Selected" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Since the ItemSource is an Observable collection, there are several options to monitor changes in the checkboxes:
Add an event handler to the item changed event of the collection and then you can add the Name or the collection index to a local collection. e.g Examples[e.CollectionIndex].Name
Alternatively iterate over the observable collection and select those Examples where Selected = "true"

wpf mvvm - TextBox/TextBlock/CheckBox that is bound to IsSelected - I need to "grab" the input there to create a new object

My textbox in xaml is this:
<TextBox Grid.Row="1" Grid.Column="1" Width="225" x:Name="cat" Margin="10"
Text="{Binding SelectedCategory.Category, Mode=TwoWay}" />
And in my viewmodel I have the following:
public string Category
{
get => _category;
set
{
_category = value;
OnPropertyChanged(() => Category);
}
}
private EventCategory _selectedCategory;
public EventCategory SelectedCategory
{
get => _selectedCategory;
set
{
_selectedCategory = value;
OnPropertyChanged(() => SelectedCategory);
}
}
public ICommand UpdateCommand { get { return new BaseCommand(ClickUpdate); } }
private async void ClickUpdate()
{
ShowMessageBox("You clicked Update!");
ButtonIsEnabled = false;
Id = SelectedCategory.Id;
Category = SelectedCategory.Category;
IsActive = SelectedCategory.IsActive;
var service = new DataService<EventCategory>(new TimeKeeprDbContextFactory());
EventCategory eventCategory = await service.Get(Id);
if (eventCategory == null)
{
ShowMessageBox("There is no such category. Please add a new one or chose another");
}
else
eventCategory = await service.Update(Id, eventCategory);
}
But of course, I'm getting an error on Category = SelectedCategory.Category;
What do I need to be able to do to "grab" the contents of the textbox (and the textblock and checkbox, of course) so that I can call my Update(Id) method?
I have it working in another class, but there my textbox was binding directly to the property:
<TextBox Grid.Row="3" Grid.Column="2" Height="18" VerticalAlignment="Center"
Text="{Binding Mode=Default, UpdateSourceTrigger=PropertyChanged, Source={StaticResource viewModel},
Path=Password, ValidatesOnExceptions=true, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" />
I'm new to mvvm and data binding, so please be gentle :D
I found the problem in a section of my View that I didn't think was relevant - I had to change
<UserControl.Resources>
<me:CategoriesViewModel x:Key="viewModel"/>
</UserControl.Resources>
to:
<UserControl.DataContext>
<me:CategoriesViewModel/>
</UserControl.DataContext>
That and a couple other small tweaks got the whole thing working.

PropertyChanged is null while drag and drop

There might be some solution out there but so far I have been unsuccessful in finding a solution to this. Any help would be appreciated. Currently I have a dialog box to which I upload files either by clicking the button or by dragging and dropping the files from the file explorer. The dialog box contains a DataGrid to which is bound an ObservableCollection<FileDetails>. An event handler for PropertyChanged has been attached as well to this property. When I add a file through button click the PropertyChanged event is fired and the UI is updated with the added file. However, when I drag and drop the file to the dataGrid the PropertyChanged handler is null.
Below is my ViewModel:
public class FileUploadVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static ObservableCollection<FileDetails> m_DialogFiles;
public ObservableCollection<FileDetails> DialogFiles
{
get
{
return m_DialogFiles ?? new ObservableCollection<FileDetails>();
}
set
{
m_DialogFiles = value;
this.NotifyPropertyChanged(nameof(DialogFiles));
}
}
public ICommand FileUpload
{
get
{
return new SimpleCommand(AddFiles);
}
}
private void AddFiles(object obj)
{
OpenFileDialog openFile = new OpenFileDialog();
openFile.Multiselect = true;
if (openFile.ShowDialog() == true)
{
m_fileList = openFile.FileNames.ToList();
GetFileWithIcon();
}
DialogFiles = new ObservableCollection<FileDetails>(dgFiles);
}
}
Below is a part of my XAML code
<Grid ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<DataGrid x:Name="dataGrid1" Height="100" ItemsSource="{Binding Path=DialogFiles, UpdateSourceTrigger=PropertyChanged}" CanUserAddRows="False" AutoGenerateColumns="False"
GridLinesVisibility="None" CanUserDeleteRows="True" HeadersVisibility="None" AllowDrop="True" Drop="dataGrid1_Drop">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding FileIcon}" Height="20" Width="20"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding FileName}" Width="Auto"></DataGridTextColumn>
<DataGridTemplateColumn Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="Delete">
<Image Source="/AssemblyName;component/Resources/delete_icon.png" Height="15" Width="20"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="60" Margin="10" Command="{Binding Path=FileUpload}">Add Files</Button>
<Button Width="60" Margin="10">Upload</Button>
</StackPanel>
</Grid>
And this is the CodeBehind
public partial class FileDialog : Window
{
private ObservableCollection<FileDetails> m_fileDialog;
public FileDialog()
{
InitializeComponent();
this.DataContext = FileUpload;
}
public FileUploadVM FileUpload
{
get
{
return new FileUploadVM();
}
}
public ObservableCollection<FileDetails> DialogFiles
{
get
{
return m_fileDialog ?? FileUpload.DialogFiles;
}
}
private void dataGrid1_Drop(object sender, DragEventArgs e)
{
string[] droppedFiles = null;
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
droppedFiles = e.Data.GetData(DataFormats.FileDrop, true) as string[];
}
if ((null == droppedFiles) || (!droppedFiles.Any()))
{
return;
}
foreach (var item in droppedFiles)
{
FileUploadVM.m_fileList.Add(item);
}
FileUpload.GetFileWithIcon();
FileUpload.DialogFiles = new ObservableCollection<FileDetails>(FileUploadVM.dgFiles);
FileUploadVM.m_fileList.Clear();
}
}
As mentioned above clicking on AddFiles works fine, but dragging and dropping does not fire PropertyChanged in spite of DialogFiles having values within.
Akshatha
I've identified a few issues with your solution:
Problem 1 - You set your DataContext (ViewModel) incorrectly, if you look at the following code:
public FileUploadVM FileUpload {
get {
return new FileUploadVM();
}
}
The above code creates a new instance of your ViewModel every time you request the ViewModel object. This means that whenever you call this object:
FileUploadVM.m_fileList.Clear();
it will create a new object, and will break your reference to your DataContext (View -> ViewModel).
EDIT
you can fix this by creating a normal property to retrieve the ViewModel object and use this instance every time you need to access your ViewModel:
private FileUploadVM fileUploadVM = new FileUploadVM();
public FileUploadVM FileUploadVM {
get { return this.fileUploadVM; }
set {
if (this.fileUploadVM != value) {
this.fileUploadVM = value;
}
}
}
Problem 2: You destroy your reference when you re-initialize your collection of DialogFiles:
FileUploadVM.DialogFiles = new ObservableCollection<FileDetails>(FileUploadVM.dgFiles);
Whenever you create a new Instance of this object, your view will need to be notified that there is a new collection that needs to be bound to and not to use the 'old' collection which you are not using anymore. One way to make sure you always use the right instance of the collection is to make it read-only:
public ObservableCollection<FileDetails> DialogFiles {
get {
return m_DialogFiles;
}
}
and only adding/clearing from that single instance of the collection, so instead of doing this:
FileUploadVM.DialogFiles = new ObservableCollection<FileDetails>(FileUploadVM.dgFiles);
The read-only proeprty will not allow you to do this, and rather use this to modify your collection:
FileUploadVM.DialogFiles.Add(new FileDetails("Your FileName"));
and if you should dlete/clear an item from the collection, you can simply use
FileUploadVM.DialogFiles.Clear();
// OR
FileUploadVM.DialogFiles.Remove(myFileDetailsItem);
// OR
FileUploadVM.DialogFiles.RemoveAt(index);
To summarize, your problem was that you created a new instance of your DialogFiles collection when hitting the Drop method:
FileUploadVM.DialogFiles = new ObservableCollection<FileDetails>(FileUploadVM.dgFiles);
and you should rather just add to that collection than re-declaring the instance:
foreach(var file in FileUploadVM.dgFiles) {
FileUploadVM.DialogFiles.Add(file);
}

How to implement PropertyChanged in order to refresh my Datagrid after my ComboxSelection has changed

Currently I have a datagrid and a combobox. I can't seem to get my datagrid to refresh on selectionchanged of my combobox. I'm getting the value (_mySelectedValue) but my datagrid never loads. I tried aDataGrid.refresh.
The grid should load when I select the Combobox.Name, which gives me the ID in _mySelectedID, which my query then uses to query the database and refill my collection. (I think..)
ViewModel
public class UserViewModel : ViewModelBase, IUserViewModel
{
public DataEntities _context = new DataEntities();
public ObservableCollection<UserChoice> userChoices { get; set; }
public ObservableCollection<CompanyName> companyNames { get; set; }
public ObservableCollection<UserTable> userTables { get; set; }
public UserViewModel()
{
GetID();
GetDataAsync();
}
public async Task GetDataAsync()
{
var serv = (from s in _context.UserChoices
join sa in _context.CompanyNames on s.CompanyID equals sa.CompanyID
where sa.CompanyID == mySelectedItem //crap left this out
select s).ToList();
userChoices = new ObservableCollection<UserChoice>(serv);
}
public async Task GetID()
{
var data = _context.CompanyNames.OrderBy(o => o.CompanyID).ToList();
CompanyNames = new ObservableCollection<CompanyName>(data);
}
public byte _mySelectedItem;
public Byte MySelectedItem
{
get { return _mySelectedItem; }
set
{
_mySelectedItem = value;
OnPropertyChanged("MySelectedItem");
}
}
}
XAML
<Grid Margin="0,0,-200,0">
<DataGrid x:Name="aDataGrid"
ItemsSource="{Binding userChoices, UpdateSourceTrigger=PropertyChanged}"
RowDetailsVisibilityMode="VisibleWhenSelected"
EnableRowVirtualization="True" AutoGenerateColumns="False"
HorizontalAlignment="Left" Height="159"
VerticalAlignment="Top" Width="337"
Margin="156,57,0,0">
<DataGrid.Columns>
<DataGridTextColumn x:Name="actChoice" Width="*" Header="Choice"
Binding="{Binding Choice, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<DataGridTextColumn x:Name="actDescription" Width="*" Header="Description"
Binding="{Binding Description, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
<Grid x:Name="grid1" HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Content="Company Name:" Grid.Column="0" HorizontalAlignment="Left" Margin="3" Grid.Row="0" VerticalAlignment="Center"/>
<ComboBox x:Name="companyNameComboBox" Grid.Column="1"
DisplayMemberPath="CompanyName"
HorizontalAlignment="Left" Height="Auto"
ItemsSource="{Binding companyNames}"
Margin="3" Grid.Row="0"
VerticalAlignment="Center" Width="120"
SelectedValuePath="CompanyID"
SelectedValue="{Binding Path= MySelectedItem, Mode= TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="2">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</Grid>
</Grid>
You need to call GetDataAsync() inside your setter for MySelectedItem, and then make userChoices raise a property changed event.
Also, you aren't awaiting any calls in your async methods, so you shouldn't make them async.
public void GetData()
{
// Also remember to plug in the correct company ID!
// I removed the join, too, because it looks like you don't need it.
// If you actual query is more complicated then feel free to add it back.
var serv = (from s in _context.UserChoices
where s.CompanyID == MySelectedItem
select s).ToList();
userChoices = new ObservableCollection<UserChoice>(serv);
}
public void GetID()
{
var data = _context.CompanyNames.OrderBy(o => o.CompanyID).ToList();
CompanyNames = new ObservableCollection<CompanyName>(data);
}
private ObservableCollection<UserChoice> _userChoices;
public ObservableCollection<UserChoice> userChoices
{
get { return _userChoices; }
set
{
_userChoices= value;
OnPropertyChanged("userChoices ");
}
}
private byte _mySelectedItem;
public Byte MySelectedItem
{
get { return _mySelectedItem; }
set
{
_mySelectedItem = value;
GetData();
OnPropertyChanged("MySelectedItem");
}
}
Edit
To make a save method (based on your comment) I would use an ICommand since you have a view model. If you do this, you will first need to install the Prism.Mvvm NuGet package to get the DelegateCommand class (in the Microsoft.Practices.Prism.Commands namespace).
Then add this code to your view model:
public void Save()
{
var context = new AADataEntities();
// Make changes to the context here...
context.SaveChanges();
}
private ICommand _saveCommand = new DelegateCommand(Save);
public ICommand SaveCommand
{
get { return _saveCommand; }
}
Now wire up the save command in the xaml like this:
<Button Content="Save" Command="{Binding SaveCommand}" />
Independent of code incoherence pointed by #Jevans92, I can see one problem, which is the SelectedValuePath + SelectedValue combination on Combobox.
You see, it only looks up for the selected value and updates it visual, but it never updates de binded SelectedValue back. So, if you have a pre-set SelectedValue and load the UserControl, then the Combobox looks up for the correct value to show, but if you change selection, it does not update the SelectedValue object bound to the control.
Long story short: Try removing both SelectedValuePath and SelectedValue and using SelectedItem instead.
If you have to deal only with selecting from the UI to the VM, then you don't event need to call PropertyChanged, as it is only used to notify the UI about changes on the VM.
But, if you need to also set the SelectedItem on the VM for the UI to show, then you will have to implement it.
Also note that, if you are dealing with different instances on your ItemsSource and your SelectedItem, you'd better override Equals + GetHashCode on that class, else it will never be looked up on your SelectedItem.

design pattern problem

Hello I have a application design problem and I home you can help me solve it....
This is my first application in silverlight and the first application using mvvm design pattern and I am not sure I am applying mvvm how I am supposed to..
The application is a dynamic application and at runtime I can add/remove usercontrols...
So I have a MainWindowView that has behind a MainWindowModel.
The MainWindowModel has a list of Workspaces witch are in fact WorkspaceModel classes...
I have multiple UserControls and everyone off them has his own view model witch inherits WorkspaceModel.
The Workspaces property is binded to a container in MainWindowView so adding to the Workspaces list a new UserControlModel will automatically add that control to the view.
Now where is my problem... I want to make this dynamically added usercontrols to interact. Lets say one user control is a tree and one is a grid... I want a method to say that Itemsource property of Grid UserControl Model (WorkspaceModel) to be binded to SelectedNode.Nodes Property from the Tree Usercontrol Model (WorkspaceModel).
The MainWindowModel has a property name BindingEntries witch has a list of BindingEntry...
BindingEntry stores the source property and the destination property of the binding like my workspacemodel_1.SelectedNode.Nodes -> workspacemodel_2.ItemSource...
Or as a variation the MainWindowView has a property ViewStateModel. This ViewStateModel class has dynamic created properties - "injected" with property type descriptors/reflections etc... So the user can define at run time the displayed usercontrols (by modifying the Workspaces list) and can define a view model (the ViewStameModel) and the binding is between workspacemodel properties and this ViewStateModel properties...
So I actually want to bind 2 view models one to another... How to do that?
Create an observer pattern?
Is the design until now totally wrong?
I hope it makes sense.....
I will try to add some sample code...the project is quite big I will try co put only the part I have mentioned in the problem desciption... I hope I will not miss any pice of code
First of all
public class MainWindowModel : ModelBase
{
private ObservableCollection<WorkspaceModel> _workspaces;
private ModelBase _userViewModel;
public MainWindowModel()
{
base.DisplayName = "MainWindowModel";
ShowTreeView(1);
ShowTreeView(2);
ShowGridView(3);
ShowGridView(4);
UserViewModel = new ViewModel(); //this is the ViewStateModel
}
void ShowTreeView(int id)
{
WorkspaceModel workspace = ControlFactory.CreateModel("TreeControlModel", id);
this.Workspaces.Add(workspace);
OnPropertyChanged("Workspaces");
SelectedWorkspace = workspace;
}
void ShowGridView(int id)
{
WorkspaceModel workspace = ControlFactory.CreateModel("GridControlModel", id);
this.Workspaces.Add(workspace);
OnPropertyChanged("Workspaces");
SelectedWorkspace = workspace;
}
public ObservableCollection<WorkspaceModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceModel>();
}
return _workspaces;
}
}
public ModelBase UserViewModel
{
get
{
return _userViewModel;
}
set
{
if (_userViewModel == value)
{
return;
}
_userViewModel = value;
OnPropertyChanged("UserViewModel");
}
}
}
snippets from MainappView
<DataTemplate x:Key="WorkspaceItemTemplate">
<Grid >
//workaround to use Type as in WPF
<Detail:DetailsViewSelector Content="{Binding}" TemplateType="{Binding}" >
<Detail:DetailsViewSelector.Resources>
<DataTemplate x:Key="TreeControlModel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<TreeControl:TreeControlView />
</DataTemplate>
<DataTemplate x:Key="GridControlModel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<GridControl:GridControlView />
</DataTemplate>
<DataTemplate x:Key="EmptyTemplate">
</DataTemplate>
</Detail:DetailsViewSelector.Resources>
</Detail:DetailsViewSelector>
</Grid>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<ItemsControl IsTabStop="False" ItemsSource="{Binding}" ItemTemplate="{StaticResource WorkspaceItemTemplate}" Margin="6,2"/>
</DataTemplate>
</UserControl.Resources>
<toolkit:HeaderedContentControl Grid.Column="2" HorizontalAlignment="Stretch" Name="hccWorkspaces" VerticalAlignment="Top" Header="Workspaces" Content="{Binding Source={StaticResource vm}, Path=Workspaces}" ContentTemplate="{StaticResource WorkspacesTemplate}"/>
public class ControlFactory
{
public static WorkspaceModel CreateModel(string type, int id)
{
switch (type)
{
case "TreeControlModel": return new TreeControlModel() { Id=id}; break;
case "GridControlModel": return new GridControlModel() { Id = id }; break;
}
return null;
}
}
public class GridControlModel : WorkspaceModel
{
#region Fields
ObservableCollection<TreeItem> _items;
TreeItem _selectedItem;
#endregion // Fields
#region Constructor
public GridControlModel()
{
base.DisplayName = "GridControlModel";
}
#endregion // Constructor
#region Public Interface
public ObservableCollection<TreeItem> Items
{
get
{
return _items;
}
set
{
if (_items == value)
return;
_items = value;
OnPropertyChanged("Items");
}
}
public TreeItem SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem.Equals(value))
{
return;
}
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
#endregion // Public Interface
#region Base Class Overrides
protected override void OnDispose()
{
this.OnDispose();
}
#endregion // Base Class Overrides
}
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid Grid.Column="2" Grid.Row="1" HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" ItemsSource="{Binding Path=SelectedTreeNode.Children}" IsEnabled="{Binding}" AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn CanUserReorder="True" CanUserResize="True" CanUserSort="True" Header="Id" Width="Auto" Binding="{Binding Id}" />
<sdk:DataGridTextColumn CanUserReorder="True" CanUserResize="True" CanUserSort="True" Header="Name" Width="Auto" Binding="{Binding Name}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
For communicating between your view models I would suggest taking a look at the Messenger implementation in MVVM Light as a simple solution.
Alternatively, the Mediator Pattern as described here might be interesting: http://marlongrech.wordpress.com/2008/03/20/more-than-just-mvc-for-wpf/

Categories

Resources