There are several "WPF Databinding Not Updating" questions on here, and I've read through them all, including the top-ranked one:
WPF DataBinding not updating?
However, I'm swapping out entire objects in my situation, and what's worse is that when I try to create a reproducible version of what I'm doing, it works in that version:
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Content="Contact Address" />
<TextBox x:Name="txtContactAddress" Text="{Binding Path=Contact.Address, Mode=OneWay}" />
<Label Content="Organization Address" />
<TextBox x:Name="txtOrgAddress" Text="{Binding Path=Organization.Address, Mode=OneWay}" />
<Button Content="Next Contact" Click="Button_Click" />
</StackPanel>
</Window>
Code-Behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public RecordWrapper CurrentRecord;
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CurrentRecord = new RecordWrapper(new ContactData());
this.DataContext = CurrentRecord;
}
}
public class RecordWrapper : INotifyPropertyChanged
{
private ContactData _Contact;
public ContactData Contact
{
get { return _Contact; }
set
{
_Contact = value;
OnPropertyChanged("Contact");
Organization = _Contact.Organization;
}
}
private OrganizationData _Organization;
public OrganizationData Organization
{
get { return _Organization; }
set
{
_Organization = value;
OnPropertyChanged("Organization");
}
}
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
}
public class ContactData
{
public string Address { get; set; }
public OrganizationData Organization { get; set; }
public ContactData()
{
Address = new Random().Next(1000, 9999) + " Contact Street";
Organization = new OrganizationData();
}
}
public class OrganizationData
{
public string Address { get; set; }
public OrganizationData()
{
Address = new Random().Next(1000, 9999) + " Org Street";
}
}
}
So this particular sample works as expected - when I click on the "Next Contact" button, it generates a new RecordWrapper object that contains a new ContactData and new OrganizationData object, and then assigns the new object to DataContext and then I see the textboxes update properly.
Since I'm replacing the entire object in the DataContext, I can even ditch the property notifications and reduce the RecordWrapper down to just:
public class RecordWrapper
{
public ContactData Contact { get; set; }
public OrganizationData Organization { get; set; }
public RecordWrapper(ContactData contactData)
{
Contact = contactData;
Organization = Contact.Organization;
}
}
...and it still works great.
My real code, however, reflects the first example.
In the real code, it seems like updating this.DataContext with a new object doesn't work. Nothing is updated, but there are no errors, either. Setting the path in XAML doesn't work, either. In fact, the only way I can get it to work is to use code-behind to set the bindings directly to the data object:
BindingOperations.SetBinding(txtContactAddress, TextBox.TextProperty, new Binding() { Source = this.RecordWrapper, Path = new PropertyPath("Contact.Address"), Mode = BindingMode.OneWay });
When I do this, I can see it working, but that's obviously missing the point of binding if I have to run code on each field each time to update the bindings.
I'm stumped because the real code is nearly identical to the example aside from having more fields, different field names, and differences that have nothing to do with data or binding. When I click on a button, this is the exact code:
this.Data = new DataBindings(CurrentContact.FetchFake());
this.DataContext = this.Data;
The FetchFake() method generates a new CurrentContact object with some fake data, and I have verified that after that first line runs, this.Data.CurrentContact.Login is populated and is a read-only property of the CurrentContact object (not a field).
...and one of the XAML textboxes looks like this:
<TextBox x:Name="txtAccountNumber" Grid.Column="1" Grid.Row="1" Width="Auto" Text="{Binding Path=CurrentContact.Login, Mode=OneWay}"/>
The result is blank. I have also tried variations on order (e.g. setting DataContext to this.Data first, then setting the this.Data.CurrentContact property and letting the property-changed notification kick off, but still no luck. But if I run:
BindingOperations.SetBinding(tabAccount.txtAccountNumber, TextBox.TextProperty, new Binding() { Source = this.Data, Path = new PropertyPath("CurrentContact.Login"), Mode = BindingMode.OneWay });
...then I'll see the value show up.
Based on these symptoms, and the fact that my first example works but the real code doesn't, can anyone think of any place I should look for some culprits?
Ughhhhh. Figured it out. I had a REALLY old line of code in there that changed the DataContext of a container element that was between the Window and the Textbox, which interrupted the correct binding. I thought I had removed all of it, but hadn't. It DID throw an error, as Erno de Weerd had suggested, but the error was lost among some of the startup logging. We can close this one out.
Related
Here I have a WPF application that is made with the MVVM structure. I am fairly new to C# WPF and am not familiar with this concept. I am attempting to switch to another view through a function in one view via the press of a button.
Here is what the application looks like,
Once the Login button is pressed a function is triggered that will validate the inputs and if valid switch to another view. Which would look like such,
File Structure
How can i switch the views ?
Below are some code for reference.
MainWindow.xaml
<Window x:Class="QuizAppV2.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:QuizAppV2"
xmlns:viewModel="clr-namespace:QuizAppV2.MVVM.ViewModel"
mc:Ignorable="d"
Height="600" Width="920"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
ResizeMode="NoResize"
Background="Transparent"
AllowsTransparency="True">
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Border Background="#272537"
CornerRadius="20">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="75"/>
<RowDefinition/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Online Quiz"
Grid.Column="1"
FontSize="20"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="2"
Margin="30,20"
Orientation="Horizontal"
HorizontalAlignment="Right"
VerticalAlignment="Top">
<Button Content="–"
Background="#00CA4E"
Style="{StaticResource UserControls}"
Click="Minimise"/>
<Button Content="▢"
Background="#FFBD44"
Style="{StaticResource UserControls}"
Click="Restore"/>
<Button Content="X"
Background="#FF605C"
Style="{StaticResource UserControls}"
Click="Exit"/>
</StackPanel>
</Grid>
<ContentControl Grid.Column="1"
Grid.Row="1"
Margin="20,10,20,50"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
</Window>
MainViewModel.cs
using QuizAppV2.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QuizAppV2.MVVM.ViewModel
{
class MainViewModel : ObservableObject
{
public RelayCommand LoginViewCommand { get; set; }
public RelayCommand SubjectSelectionViewCommand { get; set; }
public RelayCommand QuizViewCommand { get; set; }
public RelayCommand ResultViewCommand { get; set; }
public LoginViewModel LoginVM { get; set; }
public SubjectSelectionViewModel SubjectSelectVM { get; set; }
public QuizViewModel QuizVM { get; set; }
public ResultViewModel ResultVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
onPropertyChanged();
}
}
public MainViewModel()
{
LoginVM = new LoginViewModel();
SubjectSelectVM = new SubjectSelectionViewModel();
QuizVM = new QuizViewModel();
ResultVM = new ResultViewModel();
CurrentView = SubjectSelectVM;
LoginViewCommand = new RelayCommand(o =>
{
CurrentView = LoginVM;
});
SubjectSelectionViewCommand = new RelayCommand(o =>
{
CurrentView = SubjectSelectVM;
});
}
}
}
LoginView.xaml
using QuizAppV2.MVVM.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace QuizAppV2.MVVM.View
{
/// <summary>
/// Interaction logic for LoginView.xaml
/// </summary>
public partial class LoginView : UserControl
{
public LoginView()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (UsrId.Text == "" || UsrName.Text == "")
{
UsrIDErrMsg.Visibility = Visibility.Visible;
UsrNameErrMsg.Visibility = Visibility.Visible;
}
else
{
UsrIDErrMsg.Visibility = Visibility.Hidden;
UsrNameErrMsg.Visibility = Visibility.Hidden;
MainWindow.currentUser = new Student(UsrId.Text, UsrName.Text);
}
}
}
}
Thank you
I suggest using "Datatemplate". Put in the main window resources the following:
<DataTemplate DataType="{x:Type viewmodel:QuizViewModel}">
<local:QuizView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:LoginViewModel}">
<local:LoginView/>
</DataTemplate>
and so on with the others...
WPF is doing all the work for you, it examine the "CurrentView" prroperty and select how to view it according the the suitable DataTemplate.
Navigation is a tricky topic there are few ways to do this but since you are new to WPF I tried to outline a simple technique, along the lines of the examples you've provided requirement is have to go from page to page, a simple idea would be to swap out the contents. What I mean by that is when the user clicks "Login" we authenticate the user and swap the LoginPage with some other page, in your case a quiz page, when the user selection any option we swap out the view with the next view and so on.
I've coded up a simple solution with Shell mechanism. Essentially we create a empty shell in MainWindow (i.e it has no UI) and we load pages into this empty shell using a NavigationService/Helper. I've gone with a singleton class here just for simplicity, there are 3 main Methods in this,
RegisterShell : This has to be the Window where the swapping will happen, this ideally needs to be set once.
Load View : Method which Swaps out old view with the new one, I have gone with user control for this as most of the sub views can be user control in WPF.
LoadViewWithCustomData : Similar to above but has more flexibilty since it allows you to supply extra data.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace Navigation
{
class NavigationService
{
/// <summary>
/// Singleton so we keep on shell which views can use this to navigate to different pages.
/// </summary>
public static NavigationService Instance = new NavigationService();
private MainWindow myShell;
private NavigationService()
{
}
/// <summary>
/// Register the main shell so this service know where to swap the data out and in of
/// </summary>
/// <param name="theShell"></param>
public void RegisterShell(MainWindow theShell)
{
this.myShell = theShell;
}
/// <summary>
/// Swaps out any view to the shell.
/// </summary>
/// <typeparam name="T"></typeparam>
public void LoadView<T>() where T : UserControl, new()
{
myShell.TheShell = new T();
}
/// <summary>
/// Swaps out any view to the shell with custom data, here the user responsible to create UserControl with all the reqired data for the view.
/// We can automate this via reflection if required.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="theNewControl"></param>
public void LoadViewWithCustomData<T>(UserControl theNewControl) where T : UserControl, new()
{
myShell.TheShell = theNewControl;
}
}
Now here's how my LoginPage looks, the important line here is NavigationService.Instance.LoadView<_4OptionQuizPage>() this essentially sends the user to _4OptionQuizPage.
public partial class LoginPage : UserControl
{
public ICommand LoginClicked { get; }
public LoginPage()
{
InitializeComponent();
this.DataContext = this;
LoginClicked = new SimpleCommand(OnLoginClicked);
}
private void OnLoginClicked()
{
// TODO : Authenticate user here.
// Send the user to Quiz Page
NavigationService.Instance.LoadView<_4OptionQuizPage>();
}
}
And in the _4OptionQuizPage we can have something like this, this is where the bulk of business logic may reside, I have 4 buttons here, 2 of them show message box but Button 1 sends you back to LoginPage and Button 2 reloads the same page with different data (i.e sending the user to next question)
public partial class _4OptionQuizPage : UserControl, INotifyPropertyChanged
{
public ICommand Option1Clicked { get; }
public ICommand Option2Clicked { get; }
public ICommand Option3Clicked { get; }
public ICommand Option4Clicked { get; }
private string myQuestion;
public event PropertyChangedEventHandler PropertyChanged;
public string Question
{
get { return myQuestion; }
set
{
myQuestion = value;
NotifyPropertyChanged();
}
}
public _4OptionQuizPage() : this($"Question Loaded At {DateTime.Now}, this can be anything.")
{
}
public _4OptionQuizPage(string theCustomData)
{
InitializeComponent();
Question = theCustomData;
this.DataContext = this;
this.Option1Clicked = new SimpleCommand(OnOption1Clicked);
this.Option2Clicked = new SimpleCommand(OnOption2Clicked);
this.Option3Clicked = new SimpleCommand(OnOption3Clicked);
this.Option4Clicked = new SimpleCommand(OnOption4Clicked);
}
private void OnOption4Clicked()
{
MessageBox.Show("Option 4 selected, Store the results");
}
private void OnOption3Clicked()
{
MessageBox.Show("Option 3 selected, Store the results");
}
private void OnOption1Clicked()
{
NavigationService.Instance.LoadView<LoginPage>();
}
private void OnOption2Clicked()
{
NavigationService.Instance.LoadViewWithCustomData<LoginPage>(new _4OptionQuizPage("A custom question to emulate custom data"));
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Finally your MainWindow would be registering the shell and sending the user to LoginPage, and it's XAML file should not have anything in it
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object myShell;
public object TheShell
{
get { return myShell; }
set
{
myShell = value;
this.NotifyPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
NavigationService.Instance.RegisterShell(this);
NavigationService.Instance.LoadView<LoginPage>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml should be empty, essentially a shell for everything else.
<Window x:Class="Navigation.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:Navigation"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Content="{Binding TheShell}">
</Window>
This sample demonstrates two approaches to navigation. Often useful since you say want to start by logging in but not show any menus etc until the user is logged in. Then once they log in you want some sort of menu or list of views they can navigate to which remains static.
My mainwindow is purely a shell to contain everything.
It's markup is:
<Window ......
Title="{Binding Title}"
Content="{Binding}"
/>
This sample uses viewmodel first for all navigation. Viewmodels are templated out into UI.
There is more in the code behind.
public partial class LoginNavigationWindow : Window
{
public Type ParentViewModel
{
get { return (Type)GetValue(ParentViewModelProperty); }
set { SetValue(ParentViewModelProperty, value); }
}
public static readonly DependencyProperty ParentViewModelProperty =
DependencyProperty.Register(name: "ParentViewModel",
propertyType: typeof(Type),
ownerType: typeof(LoginNavigationWindow),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: null,
propertyChangedCallback: new PropertyChangedCallback(ParentViewModelChanged)
));
private static void ParentViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var vm = Activator.CreateInstance((Type)e.NewValue);
((Window)d).DataContext = vm;
Task.Run(((IInitiatedViewModel)vm).Initiate);
}
public LoginNavigationWindow()
{
InitializeComponent();
WeakReferenceMessenger.Default.Register<ParentNavigate>(this, (r, pn) =>
{
this.SetValue(LoginNavigationWindow.ParentViewModelProperty, pn.ParentViewModelType);
});
}
The messenger registration will switch out the window's datacontext using a dependency property. The message is just a class with a property to pass a Type
public class ParentNavigate
{
public Type ParentViewModelType { get; set; }
}
The callback ParentViewModelChanged takes a type, instantiates it and sets datacontext on the window.
Usually, you're not interested in retaining state of a window or parent level piece of view. You already logged in. If you wanted to log back in again then you would start again and input name and password.
The entrypoint is slightly unusual since I handle application startup and rely on that dependency property callback.
private void Application_Startup(object sender, StartupEventArgs e)
{
var mw = new LoginNavigationWindow();
mw.Show();
mw.SetValue(LoginNavigationWindow.ParentViewModelProperty, typeof(LoginViewModel));
}
Instead of a mainwindow full of menus etc I have of course got nothing.
I have a LoginUC is the first thing you will see on start up. This is just illustrative.
We will get input from the user and validate it before navigating in a real app. We're just interested in that navigation here so this version just has a button to navigate to MainViewModel:
<Grid>
<StackPanel>
<TextBlock Text="Log in"/>
<Button Content="Go"
Command="{Binding LoadMainCommand}"/>
</StackPanel>
</Grid>
</UserControl>
My LoginViewModel has a command, title and a task.
public partial class LoginViewModel : BaseParentViewModel
{
[RelayCommand]
private async Task LoadMain()
{
var pn = new ParentNavigate{ ParentViewModelType = typeof(MainViewModel) };
WeakReferenceMessenger.Default.Send(pn);
}
public LoginViewModel()
{
Title = "Please Log In first";
}
public override async Task Initiate()
{
// Get any data for login here
}
}
BaseParentViewModel
public partial class BaseParentViewModel : ObservableObject, IInitiatedViewModel
{
[ObservableProperty]
private string title = string.Empty;
virtual public async Task Initiate() { }
}
Interface
public interface IInitiatedViewModel
{
Task Initiate();
}
The purpose of this interface is to give us a generic way for any viewmodel to get any data it requires. By setting datacontext and then starting up a background thread to get that data the view will appear quickly and then fill with any data it needs. If getting that data takes a while then at least the view is "up" and visible quickly whilst the task still carries on working.
In a fuller example we would have IsBusy in a base viewmodel which would start off true and be changed to false. That would drive a "throbber" or busing indicator in the view.
A resource dictionary associates viewmodel datatemplates with usercontrols using datatype:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LoginNavigation"
>
<DataTemplate DataType="{x:Type local:MainViewModel}">
<local:MainUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SubjectsViewModel}">
<local:SubjectsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ResultViewModel}">
<local:ResultView/>
</DataTemplate>
</ResourceDictionary>
That is merged in app.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/ViewDataTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Once you login, the entire content of the window is replaced. The datacontext is changed from LoginViewModel to MainViewModel, that is then templated out into MainUC:
public partial class MainViewModel : BaseParentViewModel
{
[ObservableProperty]
private object currentChildViewModel;
[ObservableProperty]
private List<ChildViewModel> childViewModelList;
[RelayCommand]
private async Task ChildNavigation(ChildViewModel cvm)
{
if (cvm.Instance == null)
{
cvm.Instance = Activator.CreateInstance(cvm.ViewModelType);
if (cvm.Instance is IInitiatedViewModel)
{
Task.Run(((IInitiatedViewModel)cvm.Instance).Initiate);
}
}
CurrentChildViewModel = cvm.Instance;
}
public override async Task Initiate()
{
ChildViewModelList = new List<ChildViewModel>()
{
new ChildViewModel{ DisplayName="Subjects", ViewModelType= typeof(SubjectsViewModel) },
new ChildViewModel{ DisplayName="Results", ViewModelType= typeof(ResultViewModel) }
};
}
public MainViewModel()
{
Title = "Quiz";
}
}
You would probably want to have more views of course and pick one to show initially which would be setup in Initiate.
MainUC:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ChildViewModelList}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding DisplayName}"
Command="{Binding DataContext.ChildNavigationCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentPresenter Content="{Binding CurrentChildViewModel}"
Grid.Column="1"/>
</Grid>
</UserControl>
In the view you get a list of buttons in a left column which will allow navigation in the right column. But retaining MainUC of course.
Instead of a listbox this could be a menu or maybe a tabcontrol.
Clicking on a button calls a command in MainViewModel and passes the instance of ChildViewModel as a parameter.
That is then used to instantiate a viewmodel, set CurrentChildViewmodel and cache the instance.
CurrentChildViewmodel will of course itself be templated out into a usercontrol within MainUC.
public partial class ChildViewModel : ObservableObject
{
public string DisplayName { get; set; }
public Type ViewModelType { get; set; }
public object Instance { get; set; }
}
This is rather a simplistic approach and in a real world substantial app you would want dependency injection, factories and the like. But this is already quite a bit of code for a Stack Overflow answer as it is.
The remaining viewmodels and views are just simplistic implementations to prove it all works. eg
public partial class SubjectsViewModel : ObservableObject, IInitiatedViewModel
{
public async Task Initiate()
{
// Get any data for Subjects here
}
}
and
<Grid>
<TextBlock Text="Subjects"/>
</Grid>
</UserControl>
There are many ways how to allow a view model to participate in page navigation.
In general, each class that participates in navigation has to have access to your navigation API.
For example, you could move the navigation logic to a dedicated class NavigationService and provide a shared reference to every class that should be able to navigate to a different view.
Alternatively (and recommended), you can use routed commands that you handle on the MainWindow, which then delegates the command to the MainViewModel.
In this scenario each button would have to pass the destination as CommandParameter. This solution allows the particular view models to not directly participate in the navigation. You don't need to pollute your view model classes with navigation details.
The following example shows how to navigate from the QuizView to the ResultView using a RoutedCommand.
MainViewModel.cs
The MainViewModel is the only view model class that knows how to navigate and about the related details.
This enables extensibility while keeping the implementation of the view model classes simple.
In general, to enable data validation let the view models implement INotifyDataErrorInfo.
You can then query the INotifyDataErrorInfo.HasErrors property before allowing to leave a page.
class MainViewModel : ObservableObject
{
public object CurrentView { get; set; }
private Dictionary<Type, INotifyPropertyChanged> ViewModelMap { get; }
public MainViewModel()
{
this.ViewModelMap = new Dictionary<Type, INotifyPropertyChanged>
{
{ typeof(QuizVm), new QuizVm() },
{ typeof(ResultVm), new ResultVm() },
};
}
// Check if destination type is valid.
// In case the navigation source implements INotifyDataErrorInfo,
// check if the source is in a valid state (INotifyDataErrorInfo.HasEWrrors returns 'false').
// This method is called by the view. It will delegate its ICommand.CanExecute to this method
// If this method returns 'false' the command source e.g. Button will be disabled.
public bool CanNavigate(Type navigationSourceType, Type navigationDestinationType)
=> CanNavigateAwayFrom(navigationSourceType)
&& CanNavigateTo(navigationDestinationType);
private bool CanNavigateAwayFrom(Type navigationSourceType)
=> this.ViewModelMap.TryGetValue(navigationSourceType, out INotifyPropertyChanged viewModel)
&& viewModel is INotifyDataErrorInfo notifyDataErrorInfo
? !notifyDataErrorInfo.HasErrors
: true;
private bool CanNavigateTo(Type navigationDestinationType)
=> this.ViewModelMap.ContainsKey(navigationDestinationType);
// This method is called by the view. It will delegate its ICommand.Execute to this method
public void NavigateTo(Type destinationType)
{
if (this.ViewModelMap.TryGetValue(destinationType, out INotifyPropertyChanged viewModel))
{
this.CurrentView = viewModel;
}
}
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedCommand NavigateCommand { get; } = new RoutedUICommand(
"Navigate to view command",
nameof(NavigateCommand),
typeof(MainWindow));
private MainViewModel MainViewModel { get; }
public MainWindow()
{
InitializeComponent();
this.MainViewModel = new MainViewModel();
this.DataContext = this.MainViewModel;
var navigateCommandBinding = new CommandBinding(MainWindow.NavigateCommand, ExecuteNavigateCommand, CanExecuteNavigateCommand);
this.CommandBindings.Add(navigateCommandBinding);
}
private void CanExecuteNavigateCommand(object sender, CanExecuteRoutedEventArgs e)
{
if (e.Source is not FrameworkElement commandSource)
{
return;
}
Type navigationSourceType = commandSource.DataContext.GetType();
Type navigationDestinationType = (Type)e.Parameter;
e.CanExecute = this.MainViewModel.CanNavigate(navigationSourceType, navigationDestinationType);
}
private void ExecuteNavigateCommand(object sender, ExecutedRoutedEventArgs e)
{
var destinationViewModelType = (Type)e.Parameter;
this.MainViewModel.NavigateTo(destinationViewModelType);
}
}
MainWindow.xaml
To actually render the views (for example a custom Control) you need to define an implicit DataTemplate (without the x:Key directive) that has the associated view model class as DataType. The ContentControl will then automatically pick the correct one that matches the type of the ContentControl.Content property value.
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:QuizVM}">
<QuizView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ResultVM}">
<ResultView />
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentView}" />
</Window>
If a view needs to navigate, it must use the static routed command (defined and handled in the MainWindow) and pass the Type of the destination view model as CommandParameter.
This way, navigation will not pollute the view models and stays within the view.
QuizView.xaml
<QuizView>
<Button Content="Next"
Command="{x:Static local:MainWindow.NextPageCommand}"
CommandParameter="{x:Type local:ResultVM}"/>
</QuizView>
ResultView.xaml
<ResultView>
<Button Content="Back"
Command="{x:Static local:MainWindow.NextPageCommand}"
CommandParameter="{x:Type local:QuizVM}"/>
</ResultView>
Because the view model classes generally don't directly participate in the navigation,
they don't have to implement any related commands or depend on any navigation service.
Navigation is completely controlled by the MainWindow and its MainViewModel.
For optional data validation let them implement INotifyDataErrorInfo.
QuizVM.cs
class QuizVM : INotifyPropertyChnaged, INotifyDataErrorInfo
{
}
ResultVM.cs
class ResultVM : INotifyPropertyChnaged, INotifyDataErrorInfo
{
}
I'm binding two comboboxes to the same listviewcollection. The problem is that selecting a value in one combobox, causes the other combobox selected item to change to the exact value of the first combobox. They are coupled and I want them to be independent of each other.
MyListViewCollection is like this
modelsView = new ListCollectionView(MainVM.All_Models);
All_Models is an Observable collection of custom objects like this
public ObservableCollection<MLModel> All_Models { get; set; } = new ObservableCollection<MLModel>() { };
I bind two ComboBoxes to modelsview like this
<ComboBox ItemsSource="{Binding Path=modelsView}" SelectedItem="{Binding SelectedModel_Right}" SelectionChanged="RightSideModelSelection">
<ComboBox ItemsSource="{Binding Path=modelsView}" SelectedItem="{Binding SelectedModel_Left}" SelectionChanged="LeftSideModelSelection">
So everything works fine, the comboboxes contain the identical lists of items from the models view which is what I want.
I definitely have bound the selected item to two separate properties in the view model, and those are
private MLModel _selectedModel_left;
public MLModel SelectedModel_Left
{
get { return _selectedModel_left; }
set
{
SetProperty(ref _selectedModel_left, value);
}
}
private MLModel _selectedModel_right;
public MLModel SelectedModel_Right
{
get { return _selectedModel_right; }
set
{
SetProperty(ref _selectedModel_right, value);
}
}
The only thing I can think of is that they are both referencing the same object in the collection, but I'm not exactly sure how it causes this behavior.
Also the desired behavior is that each combobox be able to select and display a different item from the same collection.
Any explanation would be helpful as well as the recommended way to decouple the two comboboxes selection from each other.
EDIT: As requested here is the code to create a minimal working example
MainWindow.xaml
<Window x:Class="WpfApplication3.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:WpfApplication3"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Path=modelsView}" SelectedItem="{Binding SelectedModel_Left}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox ItemsSource="{Binding Path=modelsView}" SelectedItem="{Binding SelectedModel_Right}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
}
MLModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApplication3
{
public class MLModel
{
public string Name { get; set; }
public string Type { get; set; }
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace WpfApplication3
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModel()
{
modelsView = new ListCollectionView(All_Models);
//modelsView.Filter = modelsFilter;
}
public ListCollectionView modelsView { get; set; }
public ObservableCollection<MLModel> All_Models { get; set; } = new ObservableCollection<MLModel>() {
new MLModel() { Name = "One", Type = "TypeOne" },
new MLModel() {Name = "Two", Type = "TypeTwo" },
new MLModel() {Name = "Three", Type = "TypeThree" }
};
private MLModel _selectedModel_left;
public MLModel SelectedModel_Left
{
get { return _selectedModel_left; }
set
{
this._selectedModel_left = value;
NotifyPropertyChanged();
}
}
private MLModel _selectedModel_right;
public MLModel SelectedModel_Right
{
get { return _selectedModel_right; }
set
{
this._selectedModel_right = value;
NotifyPropertyChanged();
}
}
}
}
From the documentation:
If the target is an ItemsControl, the current item is synchronized with the selected item.
You are sharing the same ListCollectionView between both ComboBox objects. This means that the current selected item is updated in the view, and replicated in any other ItemsControl where that same view is used.
If you don't want this behavior, you need to give each ComboBox its own ListCollectionView object to bind to.
Alternatively, you can set the IsSynchronizedWithCurrentItem property of each ComboBox to false.
i tried to create somthing to quickly locate and watch files. So i created a TreeView that has StackPanels as Items. A StackPanel contains an Image an a Label.
private TreeViewItem createFile(string Name, string soureFile)
{
TreeViewItem tvi = new TreeViewItem();
StackPanel sp = new StackPanel();
Image i = new Image();
Label l_Text = new Label();
Label l_FileName = new Label();
l_FileName.Content = soureFile;
l_FileName.Width = 0;
l_Text.Content = Name;
System.Drawing.Bitmap dImg = (System.Drawing.Bitmap)Properties.Resources.ResourceManager.GetObject("Picture");
MemoryStream ms = new MemoryStream();
dImg.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
BitmapImage bImg = new BitmapImage();
bImg.BeginInit();
bImg.StreamSource = new MemoryStream(ms.ToArray());
bImg.EndInit();
i.Source = bImg;
i.Height = 20;
i.Width = 20;
sp.Name = "SP_File";
sp.Orientation = Orientation.Horizontal;
sp.Children.Add(i);
sp.Children.Add(l_Text);
sp.Children.Add(l_FileName);
tvi.Header = sp;
return tvi;
}
One can create logical folders (just for creating a structure) and add files and other folders to the folders (just references to to the actual file on the hdd). This worked fine until i tried to sort the TreeView. I read stuff on on Sorting TreeViews with
SortDescriptions.Add(new SortDescription("Header", ListSortDirection.Ascending));
Obviously this doesn't work for me since i cant exchange "Header" with "Header.StackPanel.Label.Text"
As I read a little bit further it seems I used the wrong approach to the whole thing by not using MVVM (Numerically sort a List of TreeViewItems in C#).
Since I have litte to no experience with MVVM can someone explain to me how it is best do this with MVVM? I use a List of "watchedFile" to keep the files and folders.
I basically have the the following class for a file
class watchedFile
{
public string name { get; private set; }
public string path { get; private set; }
public List<string> tags { get; private set; }
public watchedFile(string Name, string Path, List<string> Tags)
{
name = Name;
path = Path;
tags = Tags;
}
}
If path is null or empty its a folder. The TreeViewItem has a little Image which shows a little "folder" or "picture", a label which shows the "watchedFile.name" and an invisible label which contains the watchedfile.path (which is only shown as a tooltip). I guess I should do this with DataBinding so I dont need to add an invisible Label.
Questions:
How can I solve the task using MVVM?
How/where can I bind the Image to the TreeViewItem when I have just the wacthedFile.path to distinguish?
How do I sort the watched items?
How do I keep track of the TreeView level (so i can rebuild the structure from a saved file)?
Is there a way to sort a TreeView with StackPanel Items without using MVVM/Data-Binding?
Any help is highly appreciated.
Here's how to do this MVVM fashion.
First, write viewmodel classes. Here, we've got a main viewmodel that has a collection of WatchedFile instances, and then we've got the WatchedFile class itself. I've also decided to make Tag a class, instead of just using strings. This lets us write data templates in the XAML that explicitly and exclusively will be used to display Tag instances, rather than strings in general. The UI is full of strings.
The notification properties are tedious to write if you don't have a snippet. I have snippets (Steal them! They're not nailed down!).
Once you've got this, sorting is no big deal. If you want to sort the root level items, those are WatchedFile.
SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
But we'll do that in XAML below.
Serialization is simple, too: Just make your viewmodel classes serializable. The important thing here is that your sorting and serialization don't have to care what's in the treeview item templates. StackPanels, GroupBoxes, whatever -- it doesn't matter at all, because your sorting and serialization code just deals with your data classes, not the UI stuff. You can change the visual details in the data templates radically without having to worry about it affecting any other part of the code. That's what's nice about MVVM.
Viewmodels:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WatchedFile.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class WatchedFile : ViewModelBase
{
#region Name Property
private String _name = default(String);
public String Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged();
}
}
}
#endregion Name Property
#region Path Property
private String _path = default(String);
public String Path
{
get { return _path; }
set
{
if (value != _path)
{
_path = value;
OnPropertyChanged();
}
}
}
#endregion Path Property
#region Tags Property
private ObservableCollection<Tag> _tags = new ObservableCollection<Tag>();
public ObservableCollection<Tag> Tags
{
get { return _tags; }
protected set
{
if (value != _tags)
{
_tags = value;
OnPropertyChanged();
}
}
}
#endregion Tags Property
}
public class Tag
{
public Tag(String value)
{
Value = value;
}
public String Value { get; private set; }
}
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Populate();
}
public void Populate()
{
// Arbitrary test info, just for display.
WatchedFiles = new ObservableCollection<WatchedFile>
{
new WatchedFile() { Name = "foobar.txt", Path = "c:\\testfiles\\foobar.txt", Tags = { new Tag("Testfile"), new Tag("Text") } },
new WatchedFile() { Name = "bazfoo.txt", Path = "c:\\testfiles\\bazfoo.txt", Tags = { new Tag("Testfile"), new Tag("Text") } },
new WatchedFile() { Name = "whatever.xml", Path = "c:\\testfiles\\whatever.xml", Tags = { new Tag("Testfile"), new Tag("XML") } },
};
}
#region WatchedFiles Property
private ObservableCollection<WatchedFile> _watchedFiles = new ObservableCollection<WatchedFile>();
public ObservableCollection<WatchedFile> WatchedFiles
{
get { return _watchedFiles; }
protected set
{
if (value != _watchedFiles)
{
_watchedFiles = value;
OnPropertyChanged();
}
}
}
#endregion WatchedFiles Property
}
}
Code behind. Note I only added one line here to what the wizard gave me.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WatchedFile
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModels.MainViewModel();
}
}
}
And lastly the XAML:
<Window
x:Class="WatchedFile.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:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:WatchedFile"
xmlns:vm="clr-namespace:WatchedFile.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource
x:Key="SortedWatchedFiles"
Source="{Binding WatchedFiles}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Name" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<TreeView
ItemsSource="{Binding Source={StaticResource SortedWatchedFiles}}"
>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type vm:WatchedFile}"
ItemsSource="{Binding Tags}"
>
<TextBlock
Text="{Binding Name}"
ToolTip="{Binding Path}"
/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type vm:Tag}"
>
<TextBlock
Text="{Binding Value}"
/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
The XAML is less than obvious. TreeView.Resources is in scope for any child of the TreeView. The HierarchicalDataTemplates don't have an x:Key property, which makes them implicit. That means that when the TreeView's items are instantiated, each of the root items will have a WatchedFile class instance for its DataContext. Since WatchedFile has an implicit data template, that will be used to fill in its content. TreeView is recursive, so it uses HierarchicalDataTemplate instead of regular DataTemplate. HierarchicalDataTemplate adds the ItemSource property, which tells the item where to look for its children on its DataContext object. WatchedFile.Tags is the ItemsSource for the root-level tree items. Those are Tag, which has its own implicit HierarchicalDataTemplate. It doesn't have children so it doesn't use HierarchicalDataTemplate.ItemsSource.
Since all our collections are ObservableCollection<T>, you can add and remove items from any collection at any time and the UI will update automagically. You can do the same with the Name and Path properties of WatchedFile, because it implements INotifyPropertyChanged and its properties raise PropertyChanged when their values change. The XAML UI subscribes to all the notification events without being told, and does the right thing -- assuming you've told it what it needs to know to do that.
Your codebehind can grab SortedWatchedFiles with FindResource and change its SortDescriptions, but this makes more sense to me, since it's agnostic about how you're populating the treeview:
<Button Content="Re-Sort" Click="Button_Click" HorizontalAlignment="Left" />
<!-- ... snip ... -->
<TreeView
x:Name="WatchedFilesTreeView"
...etc. as before...
Code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
var cv = CollectionViewSource.GetDefaultView(WatchedFilesTreeView.ItemsSource);
cv.SortDescriptions.Clear();
cv.SortDescriptions.Add(
new System.ComponentModel.SortDescription("Name",
System.ComponentModel.ListSortDirection.Descending));
}
or the non MVVM solution would have been....
I see your header is a StackPanel with 2 children and you whish to sort on the Content of the label, which is the 2nd child
You would access the label child as an array of position [1] since arrays are 0 based.
TreeView1.Items.SortDescriptions.Clear();
TreeView1.Items.SortDescriptions.Add(new SortDescription("Header.Children[1].Content", ListSortDirection.Ascending));
TreeView1.Items.Refresh();
I am new to Entity Framework and I'm trying to learn it.
I was trying to modify an exercise found on the official documentation: I would like to have a list of fathers and a list of sons. Every son must have a father selected from a combobox menu.
Now I can do that but, if I add a father, I don't see it in the father list nor in the combobox. If I add a son I don't see the son in the son's list.
If I close and reopen the program I correctly see fathers and sons previously added.
How I can automatically upgrade data in combobox and in the listview?
I don't like to call a function to refresh, I would like to automatically make the refresh when changing something in the database.
My project is made of 3 files:
MainWindow.xaml
<Window x:Class="EF7Fam.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:EF7Fam"
mc:Ignorable="d"
Loaded="Page_Loaded"
Title="MainWindow" Height="350" Width="550">
<Grid>
<StackPanel Orientation="Horizontal">
<StackPanel Width="263" Margin="3 0 3 0">
<TextBox Name="NewFT"></TextBox>
<Button Click="Add_Click">Add</Button>
<ListView Name="Fathers">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<StackPanel Width="263" Margin="3 0 3 0">
<TextBox Name="NewSN"></TextBox>
<ComboBox Name="FT" DisplayMemberPath="Name" SelectedValuePath="FTId"/>
<Button Click="Add_SN_Click">Add</Button>
<ListView Name="Sons">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.Data.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace EF7Fam
{
/// <summary>
/// Logica di interazione per MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
using (var db = new Family())
{
db.Database.Migrate();
}
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
Fathers.ItemsSource = db.Fathers.ToList();
Sons.ItemsSource = db.Sons.ToList();
FT.ItemsSource = db.Fathers.ToList();
}
}
private void Add_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
var ft = new Father { Name = NewFT.Text };
db.Fathers.Add(ft);
db.SaveChanges();
}
}
private void Add_SN_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
var sn = new Son { Name = NewSN.Text, FTId = Int32.Parse(FT.SelectedValue.ToString()) };
db.Sons.Add(sn);
db.SaveChanges();
}
}
}
}
Model.cs
using System;
using Microsoft.Data.Entity;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Runtime.CompilerServices;
namespace EF7Fam
{
public class Family : DbContext
{
public DbSet<Father> Fathers { get; set; }
public DbSet<Son> Sons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite($"Filename=Family.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Father>()
.Property(b => b.Name)
.IsRequired();
}
}
public class Father : INotifyPropertyChanged
{
[Key]
public int FTId { get; set; }
public string Name { get; set; }
public List<Son> Sons { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Son : INotifyPropertyChanged
{
[Key]
public int SNId { get; set; }
public string Name { get; set; }
public int FTId { get; set; }
public Father Father { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I looked many times on this site and in the Web, but without finding a solution.
I will be very grateful if someone can help me to let me know what I doing wrong,
Thanks,
bye,
Enrico
To bound your controls to the DbSets you need to do this:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
db.Fathers.Load();
Fathers.ItemsSource = db.Fathers.Local;
db.Sons.Load();
Sons.ItemsSource = db.Sons.Local;
FT.ItemsSource = db.Fathers.Local;
}
}
Calling Load method you are going to load the existing objects into memory and DbSet<TEntity>.Local property will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet. Adding or Removing from the ObservableCollection will also perform the corresponding Add/Remove on the DbSet.
This way if you need to save changes after perform all the operations that you need in your view, you can define a command or override the click event of a button in your view and call the SaveChanges method of your context:
private void SaveChanges_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
db.SaveChanges();
}
}
Update
Now I saw that you are using EF7, well the true is I'm not aware about all the changes in this version, but I can tell you that there are a lot, maybe this property is not still implemented. I found a question asking about that and is not answered yet.
Digging more in documentation I think I found a solution but the true is I don't like it, but it could work until the Load property shows up. According to the EF documentation you can do this:
private void Add_Click(object sender, RoutedEventArgs e)
{
using (var db = new Family())
{
var ft = new Father { Name = NewFT.Text };
db.Fathers.Add(ft);
db.SaveChanges();
Fathers.ItemsSource = db.Fathers.ToList();
}
}
If I find a better solution I'll let you know.
I am playing with a sample WPF application that is tiered in a Model-View-Presenter manner. The Model is a collection of Animals which is displayed in a view via binding to properties in a presenter class. The XAML has an items control that displays all the animals in a model.
The model class has a boolean attribute called 'IsMammal'. I want to introduce a filter in the XAML in the form of a radio button group that filters the collection of animals based on the 'IsMammal' attribute. Selection of the radiobutton 'Mammals' updates the items control with all the Animals that have the 'IsMammal' value set to true and when the value is toggled to 'Non-Mammals', the display is updated with all animals that have that particular boolean set to false. The logic to do the filtering is extremely simple. What is troubling me is the placement of the logic. I don't want any logic embedded in the *.xaml.cs. I want the toggling of the radiobutton to trigger a logic in the presenter that tapers my animal collection to be rebound to the display.
Some guidance here will be extremely appreciated.
Thanks
I suggest you do the following in your Presenter:
=> Create a ListCollectionView field. Set it to be equal to the "Default Collection View" of your collection, and use it as the ItemsSource for the list control. Something like:
public class Presenter()
{
private ListCollectionView lcv;
public Presenter()
{
this.lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(animalsCollection);
listControl.ItemsSource = this.lcv;
}
}
=> In the handler/logic for the RadioButton's corresponding event, filter the ListCollectionView. Something like:
void OnCheckedChanged()
{
bool showMammals = radioButton.IsChecked;
this.lcv.Filter = new Predicate((p) => (p as Animal).IsMammal == showMammals);
this.lcv.Refresh();
}
Hope this helps.
EDIT:
While doing this is possible using MVP, using MVVM should be a better choice, IMHO (and as mentioned by the other answer). To help you out, I wrote a sample that implements your requirements via MVVM. See below:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Vertical">
<RadioButton x:Name="rb1" GroupName="MyGroup" Content="IsMammal = true" Checked="rb1_Checked"/>
<RadioButton x:Name="rb2" GroupName="MyGroup" Content="IsMammal = false" Checked="rb2_Checked"/>
<ListBox ItemsSource="{Binding Path=AnimalsCollectionView}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Threading;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void rb1_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(true);
}
private void rb2_Checked(object sender, RoutedEventArgs e)
{
(this.DataContext as ViewModel).UpdateFilter(false);
}
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Animal> animals = new ObservableCollection<Animal>();
private ListCollectionView animalsCollectionView;
public ListCollectionView AnimalsCollectionView
{
get { return this.animalsCollectionView; }
}
public void UpdateFilter(bool showMammals)
{
this.animalsCollectionView.Filter = new Predicate<object>((p) => (p as Animal).IsMammal == showMammals);
this.animalsCollectionView.Refresh();
}
public ViewModel()
{
this.animals.Add(new Animal() { Name = "Dog", IsMammal = true });
this.animals.Add(new Animal() { Name = "Cat", IsMammal = true });
this.animals.Add(new Animal() { Name = "Bird", IsMammal = false });
this.animalsCollectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(this.animals);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class Animal : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private bool isMammal;
public bool IsMammal
{
get { return this.isMammal; }
set
{
this.isMammal = value;
this.OnPropertyChanged("IsMammal");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
}
I came from an MVP background before learning WPF and I have come to find that adapting the MVP pattern to WPF is a difficult exercise at best. The "proper" (read, least frustrating) approach is to utilize the Model-View-ViewModel (MVVM) pattern, which makes heavy use of the databinding features of WPF to minimize the amount of code that ends up the view-behind file.
In the simplest case, you would end up with two properties on your ViewModel:
bool FilterMammals
ObservableCollection MammalsToDisplay
In the XAML, You would bind the first to your radio button group and the second to the ItemsSource of the ListBox. The WPF databinding framework will call your property setter whenever the radiobutton group value changes, and in here you can filter and then update the list of items.
I'm not sure what your familiarity with MVVM is, so I'll stop here. Let me know if more detail would help :).