Control Databinding to View not ViewModel in MVVM scenario - c#

I've been working with the MVVM model for a week or so now and I think I have a handle on what should go where now. Note the "think" in that.
I have a single ViewModel that my view (MainWindow) binds to
_ViewModel = new MainViewModel();
this.DataContext = _ViewModel;
I have a few ICommands that do work within the ViewModel and subsequently the Model, which I'm fine with.
Now I initiate a few windows from my View (MainWindow) which I do in codebehind, as it's all purely view related stuff. I am trying to replicate the ICommand setup I have in the ViewModel in the View to simplify my life, or so I thought. I have the following commands set-up:
public ICommand comInitialiseWindows { get; private set; }
private bool _windowsactive = false;
public bool WindowsActive
{
get { return _windowsactive; }
set { SetProperty(ref _windowsactive, value); }
}
public bool comInitialiseWindows_CAN()
{
return !_windowsactive;
}
private void comInitialiseWindows_DO()
{
... Code to do the window creation, etc.
}
I have this relay command in the MainWindow code:
comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
If I put this in the ViewModel it works a treat apart from the window creation stuff, but as it's View related I'm not surprised.
So the problem is the code doesn't run when I click the button. I'm guessing that the XAML is bound to the ViewModel, but I can't figure a way around this without setting the Binding for each button to the MainWindow in codebehind. I had assumed that the following would work, but it doesn't:
<Button x:Name="ribbutLayoutWindows"
Command="{Binding local:comInitialiseWindows}"
IsEnabled="{Binding local:comInitialiseWindows_CAN}"/>
I'm pretty sure I'm just not getting something somewhere. Or I'm trying to overcomplicate matters where a normal button click would have sufficed as it's View only.
Any suggestions?

There are two possibilities:
Through the ViewModel:
You could expose a Property on your ViewModel:
class MainViewModel
{
ICommand comInitialiseWindows {get; set;}
}
And in your MainWindow:
MainViewModel vm = this.DataContext as MainViewModel;
vm.comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
XAML:
<Button x:Name="ribbutLayoutWindows" Command="{Binding comInitialiseWindows}" />
Note: you don't need to bind the IsEnabled property. WPF will handle that for you and automatically call into the CanExecute-method of your ICommand.
Through a DependencyProperty
Declare this dependecyProperty in your code-behind:
public ICommand comInitialiseWindows
{
get { return (ICommand)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty comInitialiseWindowsProperty =
DependencyProperty.Register("comInitialiseWindows", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));
Assign a value in the code-behind:
comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
After that, you need to break out of your data-context in the XAML. First of all, give your Page a name:
<Window x:Class="Web_Media_Seeker_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Web_Media_Seeker_WPF"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="myWindow"
Title="MainWindow" Height="350" Width="525">
And then declare your binding as follows:
<Button x:Name="ribbutLayoutWindows" Command="{Binding comInitialiseWindows, ElementName=myWindow}" />

Related

How to use a user control dependency property when setting its data context?

I've made a user control which contains a command, to be called in response to a certain event. This command is a dependency property. I want to use it in the main window like this:
<local:myUserControl Command="{Binding someCommand}"/>
The "myCommand" is the dependency property I created for this user control. And I bind it to a command of the view model of the main window ("someCommand").
The problem is that I am setting the datacontext of my usercontrol (I have a view model for it), and it seems to reset the "Command" to null… Here is the code-behind of my view model:
public partial class myUserControl : UserControl, ICommandSource
{
public myUserControl()
{
this.DataContext = new myViewModel();
InitializeComponent();
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(myUserControl), new PropertyMetadata(null));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(myUserControl), new PropertyMetadata(0));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(myUserControl), new PropertyMetadata(null));
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
Command.Execute(this.CommandParameter);
}
}
The code of my user control could be the Following:
<UserControl x:Class="myApp.myUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:myApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock MouseUp="TextBlock_MouseUp">
</TextBlock>
</Grid>
</UserControl>
(I know that this element seems a bit silly (or useless), but I have simplified it to test what didn't worked and also in order to ask a rather simple question).
I have discovered that, if I comment the "this.DataContext = new myViewModel();" line, the binding to the command works perfectly. And when I uncomment this line and put a breakpoint in the "TextBlock_MouseUp", the "Command" property is equal to null...
Would there be a way to resolve this problem? I have some complicated code in my view model (so I'm quite forced to keep this line "this.DataContext = new myViewModel();"), and I am not sure I could find another solution than having a "Command" dependency property in my user control…
To be sure I give a maximum of informations, I have the following code in the view model of my main window:
public ICommand someCommand { get; set; }
//Constructor
public MainWindowViewModel()
{
this.someCommand = new RelayCommand((obj) => { return true; },
(obj) =>
{
//I put a breakpoint here
int dummy = 0;
});
}
(The RelayCommand class is a standard RelayCommand class, with a "Predicate" CanExecute and an "Action Execute).
I hope this question is not a duplicate… I have found several similar question, but they did not seem to answer mine...
I'm really sorry for this question which was in fact a bit silly. I hadn't understand very well what happens during a binding. I thought that this code line in the MainWindow…
<local:myUserControl Command="{Binding someCommand}"/>
…would have made an attempt to bind the UserControl's "Command" property to the "someCommand" of the datacontext of the MainWindow. In fact, as #elgonzo pointed out, the binding looks up in the UserControl's datacontext for the "someCommand" property (and not in the MainWindow's datacontext!!). Therefore, setting the UserControl's datacontext with this line…
this.DataContext = new myViewModel();
...was preventing the binding to be correctly done (since it looks for the "someCommand" property of the UserControl's datacontext, which is now "myViewModel", which does not contain "someCommand"...).
To fix this, I had to change the binding like this:
<local:myUserControl Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.someCommand}"/>
I've found this solution here: https://stackoverflow.com/a/1127964/11609068.
Maybe it is not the best way to do it (the "Path= DataContext. someCommand" make me think this, it doesn't seem very elegant), but it works. Another way to do it is to name the MainWindow (x:Name="someName"), so that the binding is a bit simpler:
<local:myUserControl Command="{Binding ElementName=someName, Path=DataContext.someCommand}"/>
Again, sorry and many thanks to #elgonzo.

How to assign keyboard shortcuts from user at runtime, WPF MVVM

As the title says, I'm looking for a way to assign keyboard shortcuts from a user at runtime, using WPF MVVM pattern. I know that I can define keyboard shortcuts at start like this:
<Window.InputBindings>
<KeyBinding Command="{Binding MyCommand}" Key="A"/>
</Window.InputBindings>
I've also seen that there's a way to parse input bindings from a user. What I'm struggling with, however, is binding an inputbinding from my ViewModel to the MainWindow's InputBinding. I don't know how to achieve this. Here's the code in my MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
And here's some sample code from my ViewModel:
public partial class MainWindowViewModel : Window, INotifyPropertyChanged
{
public MainWindowViewModel()
{
KeyBinding kb = new KeyBinding { Key = Key.T, Command = MyCommand };
this.InputBindings.Add(kb);
}
}
I know that the this.InputBindings.Add(kb); part should probably be replaced with something else; adding the keybinding to the MainWindow's InputBinding instead. However, I don't know how to do this with the MVVM pattern. Therefore: how would I go about doing this?
You might define the input bindings in the view model, but you still need to add them to the view somehow.
You could for example use an attached behaviour that does this for you:
public class InputBindingsBehavior
{
public static readonly DependencyProperty InputBindingsProperty = DependencyProperty.RegisterAttached(
"InputBindings", typeof(IEnumerable<InputBinding>), typeof(InputBindingsBehavior), new PropertyMetadata(null, new PropertyChangedCallback(Callback)));
public static void SetInputBindings(UIElement element, IEnumerable<InputBinding> value)
{
element.SetValue(InputBindingsProperty, value);
}
public static IEnumerable<InputBinding> GetInputBindings(UIElement element)
{
return (IEnumerable<InputBinding>)element.GetValue(InputBindingsProperty);
}
private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = (UIElement)d;
uiElement.InputBindings.Clear();
IEnumerable<InputBinding> inputBindings = e.NewValue as IEnumerable<InputBinding>;
if (inputBindings != null)
{
foreach (InputBinding inputBinding in inputBindings)
uiElement.InputBindings.Add(inputBinding);
}
}
}
View Model:
public partial class MainWindowViewModel
{
public MainWindowViewModel()
{
KeyBinding kb = new KeyBinding { Key = Key.T, Command = MyCommand };
InputBindings.Add(kb);
}
public List<InputBinding> InputBindings { get; } = new List<InputBinding>();
public ICommand MyCommand => ...
}
View:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="Window18" Height="300" Width="300"
local:InputBindingsBehavior.InputBindings="{Binding InputBindings}">
<Grid>
</Grid>
</Window>
If these are to be persisted so they work next time the user runs the app then you could consider creating a resource dictionary as a string or uncompiled flat file.
This would allow you to work with xaml as strings. You could write that to disk and xamlreader.load into a resource dictionary then merge it into application resources.
https://social.technet.microsoft.com/wiki/contents/articles/28797.wpf-dynamic-xaml.aspx
This approach offers several benefits:
The styling is easily persisted.
You can try it out and see what's going on.
You can write a file to disk using a model method called from your viewmodel.

UserControl DataContext Binding

I have three projects in my solution:
My main WPF Application which contains a MainWindow + MainViewModel
UserControl Library with a UserControl (ConfigEditorView)
UIProcess class with the ViewModel for the UserControl (ConfigEditorViewModel)
In my MainWindow I want to use the UserControl with the ViewModel of UIProcess.
First I set the UserControl in my MainWindow:
<TabItem Header="Editor">
<Grid>
<cel:ConfigEditorView DataContext="{Binding ConfEditModel, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</TabItem>
I don't know which of these properties I need here, so I put all together but it still doesn't work.
Then I've set this in my MainViewModel:
public ConfigEditorViewModel ConfEditModel { get; set; }
With simple method that is bound to a Button:
private void doSomething()
{
ConfEditModel = new ConfigEditorViewModel("Hello World");
}
My ConfigEditorViewModel looks basically like this:
public class ConfigEditorViewModel : ViewModelBase
{
private string _Description;
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
base.RaisePropertyChanged();
}
}
public ConfigEditorViewModel(string t)
{
Description = t;
}
}
The description is bound to a TextBox in my UserControl.
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Description}"/>
When I start the application and click the Button the TextBox should contain "Hello World" but it's empty.
What I've done wrong?
i gave you a general answer:
within a "real(a usercontrol you wanna use with different viewmodels with different property names)" usercontrol you bind just to your own DependencyProperties and you do that with ElementName or RelativeSource binding and you should never set the DataContext within a UserControl.
<UserControl x:Name="myRealUC" x:class="MyUserControl">
<TextBox Text="{Binding ElementName=myRealUC, Path=MyOwnDPIDeclaredInMyUc, Path=TwoWay}"/>
<UserControl>
if you do that you can easily use this Usercontrol in any view like:
<myControls:MyUserControl MyOwnDPIDeclaredInMyUc="{Binding MyPropertyInMyViewmodel}"/>
and for completeness: the Dependency Property
public readonly static DependencyProperty MyOwnDPIDeclaredInMyUcProperty = DependencyProperty.Register(
"MyOwnDPIDeclaredInMyUc", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public bool MyOwnDPIDeclaredInMyUc
{
get { return (string)GetValue(MyOwnDPIDeclaredInMyUcProperty); }
set { SetValue(MyOwnDPIDeclaredInMyUcProperty, value); }
}
Your view models (and, optionally, models) need to implement INotifyPropertyChanged.
Binding's aren't magic. There is no inbuilt mechanism that allows for code to be notified when a plain old property's value changes. You'd have to poll it in order to check to see if a change happened, which would be very bad, performance-wise.
So bindings will look at the objects they are bound against and see if they implement INotifyPropertyChanged and, if so, will subscribe to the PropertyChanged event. That way, when you change a property and fire the event, the binding is notified and updates the UI.
Be warned, you must implement the interface and use it correctly. This example says it's for 2010, but it works fine.

WPF Change View from a User Control MVVM

I'm trying to navigate from one View Model to another without any side panel.
For example, I have a Main Window View, this is where I load my User Control.
I have tried to access the static instance from the MainViewModel to change the Views, but it's not working.
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type vm:FirstViewModel}">
<v:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondViewModel}">
<v:SecondView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentViewModel}"/>
MainViewModel.cs
class MainviewModel : ObservableObject
{
private ObservableObject _currentViewModel = new FirstViewModel();
public ObservableObject CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
RaisePropertyChangedEvent("CurrentViewModel");
}
}
private static MainViewModel _instance = new MainViewModel();
public static MainViewModel Instance { get { return _instance; } }
}
Here, I have my FirstView, it just contains a button and several other UI designs
FirstView.xaml
<Button Command="{Binding goToSecondView}" />
FirstViewModel.cs
class FirstViewModel : ObservableObject
{
public ICommand goToSecondView
{
get
{
return new DelegateCommand(() =>
{
MainViewModel.Instance.CurrentViewModel = new SecondViewModel();
});
}
}
}
And I have a SecondView, which is similar to FirstView, just that it navigates to FirstView
I have tried searching for a solution, but so far, I have only managed to find examples that shows buttons on a panel which then allow switching of the User Control from clicking those button.
What I am trying to achieve is to enable switching of User Control via the buttons on the User Control itself, without any side panel.
Any help would be very much appreciated and would definitely aid me in my future projects.
Thank You.
You're creating two different instances of MainViewModel. The first one is probably being created by a locator or something and it's the one that your view is binding to when the window is first created. It's then creating a second instance and assigning it to its own static Instance member:
private static MainViewModel _instance = new MainViewModel();
public static MainViewModel Instance { get { return _instance; } }
It's this instance that your ICommand handler is changing when you press the button, which isn't bound to anything. Generally speaking you should be using a dependency injection framework to ensuring it's a true singleton, but for now just do something like this:
public MainViewModel()
{
Instance = this;
}
public static MainViewModel Instance { get; private set; }
Also your code calls RaisePropertyChangedEvent("CurrentViewModel")...I'm pretty sure you meant that to be RaisePropertyChanged("CurrentViewModel").
i would go another way and expose an Event from your first and Second viewmodel like
public event EventHandler<ShowMeEventArgs> ShowMeEvent;
then your main just need to subscribe to this event and can show the viewmodel. and even more your first and second viewmodel dont need to know anything from mainviewmodel

Set Label Content with a Dependency property

I have trouble to understand how dependency properties can be used between C# and xaml code.
This is a smal code example of my question
XAML code:
<Window x:Class="WpfChangeTextApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Name="statusTextLabel" Content="{Binding StatusText}"></Label>
<Button Name="changeStatusTextButton" Click="changeStatusTextButton_Click">Change Text</Button>
</StackPanel>
C# code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public string StatusText
{
get { return (string)GetValue(StatusTextProperty); }
set { SetValue(StatusTextProperty, value); }
}
// Using a DependencyProperty as the backing store for StatusText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StatusTextProperty =
DependencyProperty.Register("StatusText", typeof(string), typeof(MainWindow));
private void changeStatusTextButton_Click(object sender, RoutedEventArgs e)
{
StatusText = "Button clicked";
}
}
So, my trouble is that Label statusTextLabel dose not get updated when I click on the button. My trouble is that I don't know in what part of the code that I'm doing something wrong, is it in the xaml or in the C#? In the xaml I might doing something wrong in the Binding? Or have I missed doing something in the C# code?
By default, binding paths are relative to the DataContext property of the current element. You have not set it to anything, so it can't resolve the binding. If you want the StatusText property on your window class, then there are two approaches. One is to use a binding with a RelativeSource of FindAncestor to find the Window in the tree and bind to its properties directly:
<Label Name="statusTextLabel" Content="{Binding StatusText,
RelativeSource={RelativeSource AncestorType=Window}}"></Label>
The other is to set the DataContext of the Window to itself, so it will be inherited by the label. For example, in your constructor:
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
For most applications, you will actually want a separate class to represent the data, and you will set an instance of that class as the DataContext. You can also use ordinary CLR properties instead of dependency properties, although you will need to implement INotifyPropertyChanged if you want to UI to be informed when properties change. Dependency properties are more useful when you are writing a custom control and you want users to be able to set the properties using data binding.

Categories