I am struggling for one week now with my problem and i cant solve it. I am programming a MVVM WPF application which is having one window (MainView). In This Mainview i want to load different UserControl when i need them. In the Application-Startup I am loading the MainViewModel. In the Constructor of the MainViewModel I am loading the First ViewModel (LoginViewModel). Cause of my MainView.xaml it is showing me my Login-User-Control like i want to. So till this point everything is fine. In the ActivePanel-class i am saving the CurrentView, because in my MainView.xaml i am making a binding to my CurrentView for the ContentControl. So everything is working except the changing of the views although my NotifyPropertyChanged method of the CurrentView is working. I am thinking, that my mistake is in the xaml (DataBinding). Hope you guys can help me.
This is my MainView.xaml in which i want to load the different DataTemplates. Like I said before: The loading of the LoginViewModel via the Constructor of MainViewModel is working. The changing to other VieModels is working as well, but the DataBinding to the ContentControl is the big problem here.
<Window x:Class="App.View.MainView"
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:App.View"
mc:Ignorable="d"
xmlns:viewmodels="clr-namespace:App.ViewModels"
xmlns:views="clr-namespace:App.View"
xmlns:helper="clr-namespace:App.Helper"
Title="Betrooms" Height="500" Width="350">
<Window.Resources>
<DataTemplate x:Name="LoginUCTemplate" DataType="{x:Type viewmodels:LoginViewModel}">
<views:LoginUC DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="RegUCTemplate" DataType="{x:Type viewmodels:RegViewModel}">
<views:RegUC DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="HomeUCTemplate" DataType="{x:Type viewmodels:HomeViewModel}">
<views:HomeUC DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewmodels:ActivePanel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentView, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
</Grid>
</Window>
This is the class of my ActivePanel where i am saving the information about which ViewModel is the active one. The CurrentView is the property I am binding the Content Control to.
namespace APP.ViewModels
{
public class ActivePanel : NotifyPropertyChanged
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
}
This is my MainViewModel:
namespace App.ViewModels
{
public class MainViewModel : ActivePanel
{
private LoginViewModel _loginViewModel;
public MainViewModel()
{
_loginViewModel = new LoginViewModel();
CurrentView = _loginViewModel;
}
}
}
And this is my LoginViewModel where I am changing the value of CurrentView via an action:
namespace App.ViewModels
{
public class LoginViewModel : ActivePanel
{
#region Member
private string _username;
private string _password;
bool login = false;
private HomeViewModel _homeViewModel;
private RegViewModel _regViewModel;
UserModel User = new UserModel();
#endregion
#region Properties
public ICommand RegActionCommand { get; set; }
public ICommand LogActionCommand { get; set; }
public string GetUsername
{
get { return _username; }
set
{
if (value != _username)
{
_username = value;
OnPropertyChanged("GetUsername");
}
}
}
public string GetPassword
{
get { return _password; }
set
{
if (value != _password)
{
_password = value;
OnPropertyChanged("GetPassword");
}
}
}
#endregion
#region Constructor
public LoginViewModel()
{
this.RegActionCommand = new RelayCommand(RegAction);
this.LogActionCommand = new RelayCommand(LogAction);
}
#endregion
#region Button-Action
private void LogAction(object obj)
{
_homeViewModel = new HomeViewModel();
CurrentView = _homeViewModel;
}
private void RegAction(object obj)
{
_regViewModel = new RegViewModel();
CurrentView = _regViewModel;
}
#endregion
}
}
I hope my question is understandable: The ContenControl binding is set to CurrentView but the ContentControl is never changing although the property of CurrentView is changing.
Thanks to you all. Cheers, Paul.
In your command handler, you are changing the CurrentView property of your LoginViewModel. The ContentControl's DataContext is the MainViewModel though, so it's content is bound to the CurrentView property of the MainViewModel.
You need to set the MainViewModel's property in your command handler. There are different ways of achieving this, for example you could add a constructor parameter to the LoginViewModel to pass a reference to the MainViewModel. You can save this reference and then access it in your command handler.
Another possibilty would be to raise an event or send a message from the command in your LoginViewModel and handle it in the MainViewModel. This would reduce the coupling between your ViewModels, but depending on which mechanism and library you use it might be a little bit more complicated.
Related
I have the below problem: I have two different user controls inside a parent user control. These are trainList, which holds a list of train objects and trainView, which is an user control that shows details of the selected train in the list.
My wish is to share a variable of trainList with trainView.
What I have now is:
Parent user control:
<UserControl>
<UserControl>
<customControls:trainList x:Name="trainList"></customControls:trainList>
</UserControl>
<UserControl>
<customControls:trainView x:Name="trainView"></customControls:trainView>
</UserControl>
<TextBlock DataContext="{Binding ElementName=trainList, Path=SelectedTrain}" Text="{ Binding SelectedTrain.Id }">Test text</TextBlock>
</UserControl>
TrainList class:
public partial class TrainList : UserControl
{
public TrainList()
{
this.InitializeComponent();
this.DataContext = this;
}
public Train SelectedTrain { get; set; }
public void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Debug.Print(this.SelectedTrain.Id);
}
}
Note: The Train class implements INotifyPropertyChanged.
If I got this to work, I'd apply the binding to the trainView user control (not sure if this would work) instead to the text block.
<UserControl>
<customControls:trainView x:Name="trainView" DataContext="{Binding ElementName=trainList, Path=SelectedTrain}"></customControls:trainView>
</UserControl>
And then, I would access that variable someway from the code-behind of trainView.
(And after this, I would like to share a different variable from trainView with its parent user control, but maybe that's another question).
My current question is: could this be done this way or would I need to follow another strategy?
Take this simple view model, with a base class that implements the INotifyPropertyChanged interface, and a Train, TrainViewModel and MainViewModel class.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(
ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
}
}
}
public class Train : ViewModelBase
{
private string name;
public string Name
{
get { return name; }
set { SetValue(ref name, value); }
}
private string details;
public string Details
{
get { return details; }
set { SetValue(ref details, value); }
}
// more properties
}
public class TrainViewModel : ViewModelBase
{
public ObservableCollection<Train> Trains { get; }
= new ObservableCollection<Train>();
private Train selectedTrain;
public Train SelectedTrain
{
get { return selectedTrain; }
set { SetValue(ref selectedTrain, value); }
}
}
public class MainViewModel
{
public TrainViewModel TrainViewModel { get; } = new TrainViewModel();
}
which may be initialized in the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
DataContext = vm;
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 1",
Details = "Details of Train 1"
});
vm.TrainViewModel.Trains.Add(new Train
{
Name = "Train 2",
Details = "Details of Train 2"
});
}
The TrainDetails controls would look like this, of course with more elements for more properties of the Train class:
<UserControl ...>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Details}"/>
</StackPanel>
</UserControl>
and the parent UserControl like this, where I directly use a ListBox instead of a TrainList control:
<UserControl ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Trains}"
SelectedItem="{Binding SelectedTrain}"
DisplayMemberPath="Name"/>
<local:TrainDetailsControl Grid.Column="1" DataContext="{Binding SelectedTrain}"/>
</Grid>
</UserControl>
It would be instantiated in the MainWindow like this:
<Grid>
<local:TrainControl DataContext="{Binding TrainViewModel}"/>
</Grid>
Note that in this simple example the elements in the UserControls' XAML bind directly to a view model instance that is passed via their DataContext. This means that the UserControl know the view model (or at least their properties). A more general approach is to declare dependency properties in the UserControl class, that are bound to view model properties. The UserControl would then be independent of any particular view model.
In WPF I have been trying to figure out how to keep a views dependency property and one of it's view model's properties in sync for a while now without any luck. I have done a fair amount research into the subject but none of the suggested solutions are working for me and I was hoping someone could help me find what I am missing.
I attempted many of the things suggested in this post, Twoway-bind view's DependencyProperty to viewmodel's property?, because of all the things I read it looked to be the most promising, but was never able to get the results I was looking for.
I have written a simple program to demonstrate that issue I am having. In it I set the the property IntValue in MainWindowViewModel to 2 and then Bind it to a dependency property created in the UserControl IncrementIntView. Then when I push the button in IncrementIntView it increases the value of IntValue by one. This all works fine inside the UserControl IncrementIntView but I can't figure out how to send the updated IntValue back to MainWindowViewModel, it stays set to 2.
IncrementIntView.xaml.cs
public partial class IncrementIntView : UserControl
{
public int IntValue
{
get { return (int)GetValue(IntValueProperty); }
set { SetValue(IntValueProperty, value); }
}
public static readonly DependencyProperty IntValueProperty =
DependencyProperty.Register("IntValue", typeof(int), typeof(IncrementIntView),
new PropertyMetadata(-1, new PropertyChangedCallback(IntValueChanged)));
private static void IntValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
IncrementIntView detailGroup = dependencyObject as IncrementIntView;
if (e.NewValue != null)
{
detailGroup.ViewModel.IntValue = (int)e.NewValue;
}
}
public IncrementIntView()
{
InitializeComponent();
}
}
IncrementIntViewModel.cs
public class IncrementIntViewModel : ViewModelBase
{
private int intValue;
public int IntValue
{
get { return intValue; }
set { SetProperty(ref intValue, value); }
}
public IncrementIntViewModel()
{
incrementIntCommand = new Command(IncrementInt);
}
private Command incrementIntCommand;
public Command IncrementIntCommand { get { return incrementIntCommand; } }
public void IncrementInt()
{
IntValue++;
}
}
IncrementIntView.xaml
<UserControl.DataContext>
<local:IncrementIntViewModel x:Name="ViewModel" />
</UserControl.DataContext>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding IntValue}" />
<Button Content="Increment" Command="{Binding IncrementIntCommand}" Width="75" />
</StackPanel>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private int intValue = 2;
public int IntValue
{
get { return intValue; }
set { SetProperty(ref intValue, value); }
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainWindowViewModel x:Name="ViewModel"/>
</Window.DataContext>
<Grid>
<StackPanel Margin="10">
<local:IncrementIntView IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding IntValue}" />
</StackPanel>
</Grid>
I can see that your code is passing the IntValue from MainWindowViewModel's property to IncrementIntView's dependency property to IncrementIntViewModel's property.
The increment button is updating the IncrementIntViewModel's IntValue property. Unfortunately, whatever happens in the IncrementIntViewModel is not being reflected back to the IncrementIntView's IntValue dependency property. The TwoWay Mode is not between IncrementIntView's dependency property and IncrementIntViewModel's property, but it is between MainWindowViewModel's property to IncrementIntView's dependency property.
The easy solution: Bind the MainWindow's Label to IncrementIntViewModel's IntValue property without bothering the View's property.
<local:IncrementIntView x:Name="iiv" IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding DataContext.IntValue, ElementName=iiv}" />
<!--You need to specify DataContext.IntValue, because you have same name for both view's dependency property and viewmodel's property-->
Here you can see that MainWindowViewModel's IntValue is not that important, because it just passes the value to IncrementIntViewModel once and never have the value updated ever.
The other solution: You need to trigger value change back to MainViewModel's property.
First thing first, there is no connection between MainViewModel and IncrementIntViewModel. One solution is to make the MainViewModel to be singleton, so that when increment is done inside the IncrementIntViewModel, you want to update the MainViewModel's property as well.
In MainViewModel.cs
public static MainWindowViewModel SingletonInstance { get; set; }
public MainWindowViewModel()
{
if (SingletonInstance == null)
{
SingletonInstance = this;
}
}
In IncrementIntViewModel.cs
public void IncrementInt()
{
IntValue++;
MainWindowViewModel.SingletonInstance.IntValue = IntValue;
}
The other other solution: Similar to the above solution, but we don't need to make Singleton instance of MainWindowViewModel, because MainWindow is singleton to begin with.
In IncrementIntViewModel.cs
public void IncrementInt()
{
IntValue++;
((MainWindowViewModel)App.Current.MainWindow.DataContext).IntValue = IntValue;
}
If your intention is to update the IntValue from IncrementViewModel's property to IncrementView's dependency property, then you might ask why you need to do this, because MVVM is supposed to separate between V and VM. V should be looking to VM, but not the other way around.
ICommand:
public class CMDAddEditUser : ICommand
{
public event EventHandler CanExecuteChanged;
public VMAddEditUser ViewModel { get; set;}
public CMDAddEditUser()
{
}
public CMDAddEditUser(VMAddEditUser vm)
{
ViewModel = vm;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.ViewModel.SimpleMethod();
}
}
ViewModel:
public class VMAddEditUser
{
private Employee _employee = new Employee();
private CMDAddEditUser Command { get; set; }
public VMAddEditUser()
{
Command = new CMDAddEditUser(this);
}
public string txtFirstName
{
get { return _employee.FirstName; }
set { _employee.FirstName = value; }
}
public void SimpleMethod()
{
txtFirstName = "abc";
}
}
XAML:
<Window x:Class="WPF.AddEditUserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel"
Title="AddEditUserView" Height="392.329" Width="534.143">
<Grid Margin="0,0,2,-3">
<Grid.Resources>
<vm:VMAddEditUser x:Key="abx"/>
</Grid.Resources>
<Grid.DataContext>
<vm:VMAddEditUser/>
</Grid.DataContext>
<Button x:Name="btn" Content="Cancel" Command="{Binding SimpleMethod, Source={StaticResource abx}}"/>
</Grid>
</Window>
The CMDAddEditUser and VMAddEditUser is in the same project while the xaml is in a different project.
The .Execute(Object Parameter) of the ICommand doesn't seem to work. I can't bind the SimpleMethod with the button that I have. When I type the Command Binding in the xaml file, the auto-complete/suggestions only shows the txtFirstName and not the SimpleMethod. I can't figure out why the SimpleMethod can't be binded and can't be found. What did I do wrong in this code?
First: All properties you want your view to be able to bind to, must be public. Since view binds to property, which is instance of ICommand implementation, property must be public, so view can access it. However, your SimpleMethod() can be private if you don't wanna expose it to the outside world, that why you have command calling it instead of letting view directly call it.
Second: You set you grids DataContext to your 'VMEditUser' class, so in binding there is no need to specify Source, DataContext is source.
A bit silly question, but somehow I can't find how to bind the DataContext of the Window or its Content (e.g a Grid panel) to one specific property of the Window (say, ViewModel in my example below):
Code:
internal partial class MyWin : Window
{
public MyViewModelType ViewModel { get; set; }
...
}
XAML:
<Window x:Class="MyNs.MyWin"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}" />
<Grid DataContext={Binding ViewModel}> <!-- doesn't work??? -->
...
</Grid>
</Window>
I think you have this the wrong way around
if your window does the hooking up, it will work okay
public partial class MyWindow
{
public MyWindow()
{
InitializeComponent();
DataContext = ViewModel = new MyViewModelType();
}
}
Please define field for the viewmodel as it is not changing and implement INPC
private MyViewModelType viewmodel;
public MyViewModelType ViewModel
{
get
{
if(viewmodel == null)
{
viewmodel = new MyViewModelType();
}
return viewmodel;
}
set
{
viewmodel = value;
OnPropertyChanged("ViewModel")
}
}
Rest of code remains the same.
I'm trying to make Avalon MVVM compatible in my WPF application. From googling, I found out that AvalonEdit is not MVVM friendly and I need to export the state of AvalonEdit by making a class derived from TextEditor then adding the necessary dependency properties. I'm afraid that I'm quite lost in Herr Grunwald's answer here:
If you really need to export the state of the editor using MVVM, then I suggest you create a class deriving from TextEditor which adds the necessary dependency properties and synchronizes them with the actual properties in AvalonEdit.
Does anyone have an example or have good suggestions on how to achieve this?
Herr Grunwald is talking about wrapping the TextEditor properties with dependency properties, so that you can bind to them. The basic idea is like this (using the CaretOffset property for example):
Modified TextEditor class
public class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public static DependencyProperty CaretOffsetProperty =
DependencyProperty.Register("CaretOffset", typeof(int), typeof(MvvmTextEditor),
// binding changed callback: set value of underlying property
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.CaretOffset = (int)args.NewValue;
})
);
public new string Text
{
get { return base.Text; }
set { base.Text = value; }
}
public new int CaretOffset
{
get { return base.CaretOffset; }
set { base.CaretOffset = value; }
}
public int Length { get { return base.Text.Length; } }
protected override void OnTextChanged(EventArgs e)
{
RaisePropertyChanged("Length");
base.OnTextChanged(e);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Now that the CaretOffset has been wrapped in a DependencyProperty, you can bind it to a property, say Offset in your View Model. For illustration, bind a Slider control's value to the same View Model property Offset, and see that when you move the Slider, the Avalon editor's cursor position gets updated:
Test XAML
<Window x:Class="AvalonDemo.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:avalonExt="clr-namespace:WpfTest.AvalonExt"
DataContext="{Binding RelativeSource={RelativeSource Self},Path=ViewModel}">
<StackPanel>
<avalonExt:MvvmTextEditor Text="Hello World" CaretOffset="{Binding Offset}" x:Name="editor" />
<Slider Minimum="0" Maximum="{Binding ElementName=editor,Path=Length,Mode=OneWay}"
Value="{Binding Offset}" />
<TextBlock Text="{Binding Path=Offset,StringFormat='Caret Position is {0}'}" />
<TextBlock Text="{Binding Path=Length,ElementName=editor,StringFormat='Length is {0}'}" />
</StackPanel>
</Window>
Test Code-behind
namespace AvalonDemo
{
public partial class TestWindow : Window
{
public AvalonTestModel ViewModel { get; set; }
public TestWindow()
{
ViewModel = new AvalonTestModel();
InitializeComponent();
}
}
}
Test View Model
public class AvalonTestModel : INotifyPropertyChanged
{
private int _offset;
public int Offset
{
get { return _offset; }
set
{
_offset = value;
RaisePropertyChanged("Offset");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
You can use the Document property from the editor and bind it to a property of your ViewModel.
Here is the code for the view :
<Window x:Class="AvalonEditIntegration.UI.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AvalonEdit="clr-namespace:ICSharpCode.AvalonEdit;assembly=ICSharpCode.AvalonEdit"
Title="Window1"
WindowStartupLocation="CenterScreen"
Width="500"
Height="500">
<DockPanel>
<Button Content="Show code"
Command="{Binding ShowCode}"
Height="50"
DockPanel.Dock="Bottom" />
<AvalonEdit:TextEditor ShowLineNumbers="True"
Document="{Binding Path=Document}"
FontFamily="Consolas"
FontSize="10pt" />
</DockPanel>
</Window>
And the code for the ViewModel :
namespace AvalonEditIntegration.UI
{
using System.Windows;
using System.Windows.Input;
using ICSharpCode.AvalonEdit.Document;
public class ViewModel
{
public ViewModel()
{
ShowCode = new DelegatingCommand(Show);
Document = new TextDocument();
}
public ICommand ShowCode { get; private set; }
public TextDocument Document { get; set; }
private void Show()
{
MessageBox.Show(Document.Text);
}
}
}
source : blog nawrem.reverse
Not sure if this fits your needs, but I found a way to access all the "important" components of the TextEditor on a ViewModel while having it displayed on a View, still exploring the possibilities though.
What I did was instead of instantiating the TextEditor on the View and then binding the many properties that I will need, I created a Content Control and bound its content to a TextEditor instance that I create in the ViewModel.
View:
<ContentControl Content="{Binding AvalonEditor}" />
ViewModel:
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
// ...
private TextEditor m_AvalonEditor = new TextEditor();
public TextEditor AvalonEditor => m_AvalonEditor;
Test code in the ViewModel (works!)
// tests with the main component
m_AvalonEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
m_AvalonEditor.ShowLineNumbers = true;
m_AvalonEditor.Load(#"C:\testfile.xml");
// test with Options
m_AvalonEditor.Options.HighlightCurrentLine = true;
// test with Text Area
m_AvalonEditor.TextArea.Opacity = 0.5;
// test with Document
m_AvalonEditor.Document.Text += "bla";
At the moment I am still deciding exactly what I need my application to configure/do with the textEditor but from these tests it seems I can change any property from it while keeping a MVVM approach.