Trouble adding to Database - c#

So I currently have a Datagrid, When the notes in the Datagrid are selected, It populates the Textbox Fields. That part works completely. I want to implement an "AddNewNote Button" the issue I currently have is that if an item was never selected, I get a nullreference. If an item was selected before hitting the button it works! But I need it to work in both scenarios.
private NoteDTO selectedNote;
public NoteDTO SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
this.OnPropertyChanged("SelectedNote");
}
}
xaml side
<DataGrid ItemsSource="{Binding Notes}" SelectedItem="{Binding SelectedNote}" />
<TextBox Text="{Binding SelectedNote.Subject}" />
<toolkit:RichTextBox Text="{Binding SelectedNote.Comments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
public void AddNewNote()
{
var newNote = new Note();
newNote.Person_Id = PersonId;
newNote.Comments = SelectedNote.Comments;
newNote.Subject = SelectedNote.Subject;
using (var ctx = DB.Get())
{
ctx.Notes.Add(newNote);
ctx.SaveChanges();
}
this.OnPropertyChanged("newNote");
}

You're trying to bind to properties on SelectedNote, which is causes the exception when it's null:
<TextBox Text="{Binding SelectedNote.Subject}" />
<toolkit:RichTextBox Text="{Binding SelectedNote.Comments, ... }" />
You could handle this in the getter/setter, making sure that SelectedNote is never null:
get { return this.selectedNote ?? (this.selectedNote = new NoteDTO()); }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value ?? new NoteDTO(); // make sure it's never `null`
this.OnPropertyChanged("SelectedNote");
}

Related

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.

Trouble binding to property in uwp MVVM project

I am having a problem with binding from a view to a Viewmodel property.(UWP)
<AppBarToggleButton Label="Active" Icon="People"
IsChecked="{x:Bind ViewModel.IsStatusBtnChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Click="{x:Bind ViewModel.empStautsBtnClicked}"/>
private bool isStatusBtnChecked = true;
public bool IsStatusBtnChecked
{
get { return isStatusBtnChecked; }
set { Set(ref isStatusBtnChecked, value); }
}
When I try to get the value from a method to load combobox items the value is allways the default value
private List<string> depcombo;
public List<string> Depcombo
{
get { return depcombo; }
set
{
if (depcombo != value)
{
depcombo = value;
OnPropertyChanged("Depcombo");
}
}
}
public async void GetDepCombo()
{
List<string> _dep = new List<string>();
var data2 = await SqlServerDataService.GetAllEmployeesAsync();
var depResult = (from emp in EmpItems
where emp.Status == IsStatusBtnChecked
select emp.Department).Distinct();
foreach (var item in depResult)
{
if (item != null)
{
_dep.Add(item);
}
}
Depcombo = _dep;
}
When I load the data for Employyes it works fine
public async Task LoadDataAsync(MasterDetailsViewState viewState)
{
EmpItems.Clear();
var data = await SqlServerDataService.GetAllEmployeesAsync();
data.Where(em => em.Status == IsStatusBtnChecked).ToList().ForEach(p => EmpItems.Add(p));
if (viewState == MasterDetailsViewState.Both)
{
Selected = EmpItems.FirstOrDefault();
}
}
Some help will be much appreciated
When I try to get the value from a method to load combobox items the value is allways the default value
It's is confused that which is the combobox ItemsSource, if Depcombo is ComboBox ItemsSource, You have passed a new list instance to ItemsSource when you call GetDepCombo method.
Depcombo = _dep;
So, we need to set bind mode as OneWay(OneTime is default) that could response the object instance change.
<ComboBox
Margin="0,120,0,0"
ItemsSource="{x:Bind MainViewModel.Depcombo, Mode=OneWay}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If EmpItems is ComboBox ItemsSource, and it is ObservableCollection type. When you call LoadDataAsync, EmpItems clear the items fist, then add the new items. And this processing does not change the EmpItems instance object. it could works in onetime mode.
<ComboBox Margin="0,120,0,0" ItemsSource="{x:Bind MainViewModel.EmpItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<AppBarToggleButton Label="Active" Icon="People"
x:Name="empStatusBtn"
IsChecked="{x:Bind ViewModel.IsStatusBtnChecked, Mode=TwoWay}"
Click="{x:Bind ViewModel.empStautsBtnClicked}"/>
public async void empStautsBtnClicked()
{
await LoadDataAsync(MasterDetailsViewState.Both);
}
Looks like the problem is when the view is reloaded. Is there a way to refresh the view without reloading. When reloading the value of
private bool isStatusBtnChecked = true;
public bool IsStatusBtnChecked
{
get { return isStatusBtnChecked; }
set
{
if (isStatusBtnChecked != value)
{
isStatusBtnChecked = value;
OnPropertyChanged("IsStatusBtnChecked");
}
}
}
is true but the button isChecked property is false;

combobox selection changed behaviour?

I am developing a WPF application in which i have a ComboBox,like this
<ComboBox SelectedIndex="1" Grid.Column="2" Grid.Row="1" ItemsSource="{Binding VipCodes}"
SelectedItem="{Binding SelectedVipCode,Mode=OneWay}" Style="{StaticResource DefaultComboBoxStyle}" x:Name="vipCode" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
after loading the page when the selection changed, i need to update a value of a property.
I did like hooking up the selection changed event and set the value.
But when the page loaded, the event fires and the value of a property is set.
how can i bypass this?
Just set a global variable if you absolutely have to. var SkipOnce = true; and then on page load set it to false at the end. And then in your selection changed event add: if (SkipOnce==false) { //do stuff }
Described behavior can be achieved without event handlers in code behind, by ViewModel only. If DataContext of ComboBox is set to view model, then following code will do the job:
public MainWindowViewModel()
{
for (int i = 0; i < 10; i++)
{
_vipCodes.Add(new VipCode() { Description = i.ToString() });
}
SelectedVipCode = _vipCodes[3];
}
private ObservableCollection<VipCode> _vipCodes = new ObservableCollection<VipCode>();
public ObservableCollection<VipCode> VipCodes
{
get { return _vipCodes; }
}
private VipCode _selectedVipCode;
public VipCode SelectedVipCode
{
get { return _selectedVipCode; }
set
{
_selectedVipCode = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}

Using LinQ to filter ObservableCollection

I have a MVVM application and I am trying to make filtering through LinQ work on my ObservableCollection that is gotten from database based on Entity Framework.
In View Model I have this:
public class MenuListViewModel : BaseViewModelCollection<Menu>
{
private string filterString;
public string FilterString
{
get { return filterString; }
set
{
if (Equals(value, filterString)) return;
filterString = value;
RaisePropertyChanged();
}
}
//TODO problems with notification, filter doesn't work
public ObservableCollection<Menu> FilteredItems
{
get
{
if (filterString == null) return Items; //Items is Observable Collection that contains every Item
var query = Items.Where(x => x.Time.ToString().StartsWith(filterString));
return new ObservableCollection<Menu>(query);
}
}
public MenuListViewModel(MenuService menuService)
{
base.Service = menuService; //Using IoC to get service
}
}
In Xaml I have the following Binding:
<TextBox x:Name="RecipeFilterBox" Margin="5,5,0,0" TextWrapping="Wrap" Text="{Binding FilterString, NotifyOnTargetUpdated=True}" Grid.Column="1" Height="47.07" VerticalAlignment="Top"/>
The thing is that when I write anything in the TextBox, nothing changes. I know that there is something wrong with the propertyChanged event, but I really can't figure out how to fix this. If you need any more information about this app, just ask me.
EDIT:
The xaml for FilteredItems looks like this:
<ListBox x:Name="MenuItemsListView" ItemsSource="{Binding FilteredItems}" SelectedItem="{Binding DeletedItem, Mode=OneWayToSource}" Foreground="#FFFFEDD3" FontFamily="Segoe Print" FontWeight="Bold" FontSize="18.667" Grid.ColumnSpan="3" Grid.Row="1" ItemContainerStyle="{DynamicResource ListBoxItemStyle1}" Style="{DynamicResource ListBoxStyle1}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Recipe.Name}" Width="255"/>
<TextBlock Width="175" Text="{Binding Time, Converter={StaticResource EnumTimeToItsDescriptionValueConverter}, Mode=OneWay}" />
<TextBlock Text="{Binding Date, StringFormat=dd.MM.yyyy}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
you can achieve this using ICollectionView.
use FilteredItems as the underlying source of the ICollectionView and expose ICollectionView to your view instead of ObservableCollection<Menu>
Use the filter delegate to provide the filter logic
FilteredItems.Filter = item =>
{
Menu m = item as Menu;
return m.Time.ToString().StartsWith(FilterString);
}
and when FilterString changes invoke FilterItems.Refresh();
Here is an example:
public class MenuListViewModel : BaseViewModelCollection<Menu>
{
public MenuListViewModel()
{
var data = new List<Menu> { some data ... }; // your real list of menus
// initialize the collection view
FilteredItems = CollectionViewSource.GetDefaultView(data);
// apply filtering delegate
FilteredItems.Filter = i =>
{
// This will be invoked for every item in the underlying collection
// every time Refresh is invoked
if (string.IsNullOrEmpty(FilterString)) return true;
Menu m = i as Menu;
return m.Time.ToString().StartsWith(FilterString);
};
}
private string filterString;
public string FilterString
{
get { return filterString; }
set
{
if (Equals(value, filterString)) return;
filterString = value;
FilteredItems.Refresh(); // tirggers filtering logic
RaisePropertyChanged("FilterString");
}
}
public ICollectionView FilteredItems { get; set; }
}
You would also have to change the UpdateSourceTrigger on your filter TextBox to make it update the FilterString every time the user changes the text.
Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged, ...}
Add RaisePropertyChanged("FilteredItems") inside FilterString setter. FilteredItems property changed is never raised so bindings doesn't work the way you expect.

Nested Data Binding using MVVM in WPF not working

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.

Categories

Resources