There are no errors. I am trying to learn MVVM. The setup is simple. I get no output when clicking on the button. Xaml should be ok since I have produced interactions part in Blend by dragging behavior to the button. Note: My intention to use methods, but not command, since commands cover only click, but not for example DoubleClick.
<Window
x:Class="MVVM_1.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:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MVVM_1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d"
>
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Button x:Name="button"
Width="75"
Margin="198,168,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="TestMethod">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown" SourceName="button">
<ei:CallMethodAction MethodName="MethodTesting"
TargetObject="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
using System.Windows;
namespace MVVM_1
{
public class ViewModel
{
public static void MethodTesting()
{
MessageBox.Show("Success!");
}
}
}
You need to use a command to bind the control to the click event.
MVVM - Commands, RelayCommands and EventToCommand
Instead of:
public static void MethodTesting()
{
MessageBox.Show("Success!");
}
Use this example:
public ICommand ButtonClickCommand
{
get { return new DelegateCommand<object>(FuncToCall, FuncToEvaluate);}
}
private void FuncToCall(object context)
{
//this is called when the button is clicked
MessageBox.Show("Success!");
}
private bool FuncToEvaluate(object context)
{
//this is called to evaluate whether FuncToCall can be called
//for example you can return true or false based on some validation logic
return true;
}
Also under this How do you create an OnClick command in WPF MVVM with a programmatically created button? - is another great example.
Related
I am trying to catch a RountedEvent fired from multiple instances of a UserControl. This works in the non-MVVM world easily by catching the routed event at the Window level (local:MyControl.MyEvent="Window_CodeBehindHandler"). But I need to forward that event on to the View Model, and I am struggling to get an Interaction.Triggers setup to behave the same. The EventTrigger behaves as expected if I pass in a specific SourceName or SourceObject or place the Interaction.Triggers inside of the UserControl in the XAML, but that doesn't work the same.
I have tried various versions of setting SourceName or SourceObject to the control type or name without luck.
I've put together an MRE here: WpfInteractionEventTriggerMRE. The "real" implementation has an ItemsView bound to a collection created at runtime of these control objects, with a lot more going on than a single "Select" event, so "just use an {x} control with Click already implemented and restyle it" suggestions aren't helpful.
<Window x:Class="WpfApp5.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:WpfApp5"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
local:ElipseControl.Select="Window_Select"> <!-- This works as expected -->
<b:Interaction.Triggers> <!-- This does not -->
<b:EventTrigger EventName="Select">
<b:InvokeCommandAction Command="{Binding ElipseSelectedCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:ElipseControl Grid.Row="0" ElipseName="One" />
<local:ElipseControl Grid.Row="1" ElipseName="Two" />
</Grid>
</Window>
I am hoping there is something I am just overlooking here.
The EventTrigger that you are using doesn't support attached events. You will either have to implement your own custom event trigger or you could simply invoke the command from the Window_Select event handler in the window:
var viewModel = DataContext as YourViewModel;
if (viewModel != null)
viewModel.ElipseSelectedCommand.Execute(null);
This doesn't break the MVVM pattern in any way since you are invoking the very same command from the very same view compared to if you have used an interaction trigger. MVVM isn't about eliminating code from the view - it's about separation of concerns.
The custom event trigger worked. The MRE in the original question is updated. Here is the relevant updated code.
<Window x:Class="WpfApp5.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:WpfApp5"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<b:Interaction.Triggers>
<local:RoutedEventTrigger RoutedEvent="local:ElipseControl.Select" >
<b:InvokeCommandAction Command="{Binding ElipseSelectedCommand}" PassEventArgsToCommand="True" />
</local:RoutedEventTrigger>
</b:Interaction.Triggers>
<Grid>
<ItemsControl ItemsSource="{Binding Elipses}" />
</Grid>
</Window>
As per custom event trigger, a slightly updated version:
public class RoutedEventTrigger : EventTriggerBase<DependencyObject>
{
private RoutedEventHandler _eventHandler;
public RoutedEvent RoutedEvent { get; set; }
public RoutedEventTrigger()
{
}
protected override void OnAttached()
{
var associatedElement = GetAssociatedElement();
if (associatedElement != null && RoutedEvent != null)
{
_eventHandler = new RoutedEventHandler(OnRoutedEvent);
associatedElement.AddHandler(RoutedEvent, _eventHandler);
}
}
protected override void OnDetaching()
{
var associatedElement = GetAssociatedElement();
if (associatedElement != null && RoutedEvent != null && _eventHandler != null)
{
associatedElement.RemoveHandler(RoutedEvent, _eventHandler);
}
base.OnDetaching();
}
private FrameworkElement GetAssociatedElement()
{
FrameworkElement associatedElement = null;
if (AssociatedObject is Behavior behavior)
{
associatedElement = ((IAttachedObject)behavior).AssociatedObject as FrameworkElement;
}
else if (AssociatedObject is FrameworkElement frameworkElement)
{
associatedElement = frameworkElement;
}
if (associatedElement == null)
{
throw new ArgumentException("Routed Event Trigger can only be associated to framework elements");
}
return associatedElement;
}
private void OnRoutedEvent(object sender, RoutedEventArgs e) => base.OnEvent(e);
protected override string GetEventName() => RoutedEvent?.Name ?? string.Empty;
}
I'm new to WPF so bear with me. I have a WinForms Application that I am trying to redo in WPF. In my current WinForms Application, I stick all my Controls into one Form, and hide/show them based on what buttons are hit, as well as making use of a second form.
My goal: Create different views to switch between smoothly based on what button is hit, instead of hiding Controls or making separate Forms and then hiding those.
I currently have a MainWindow view (My initial launch window), where with a button, I switch to my CreateAccount view. What I am having issues with is, how can I make my button in my CreateAccount go "back" to my MainWindow?
My end goal is to be able to switch between 4 views based off Button clicks.
Here is my MainWindow.xaml
<Window x:Class="MusicPlayer.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:MusicPlayer"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">
<Views:CreateAccountView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>
<PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
My MainWindow.xaml.cs
using System;
using System.Windows;
using MusicPlayer.ViewModels;
namespace MusicPlayer {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Current.Shutdown();
} //end of onClosed
private void CreateAccountView_Clicked(object sender, RoutedEventArgs e) {
DataContext = new CreateAccountViewModel();
} //end of CreateAccountView_Clicked
}
}
And here is my CreateAccount.xaml
<UserControl x:Class="MusicPlayer.Views.CreateAccountView"
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:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
</UserControl.Resources>
<Grid Background="White">
<Button Content="Button" HorizontalAlignment="Left" Margin="276,279,0,0" VerticalAlignment="Top" Height="60" Width="59" Click="Button_Click"/>
</Grid>
</UserControl>
And my CreateAccountView.xaml.cs
using System.Windows;
using System.Windows.Controls;
using MusicPlayer.ViewModels;
namespace MusicPlayer.Views {
public partial class CreateAccountView : UserControl {
//public static readonly DependencyProperty TestMeDependency = DependencyProperty.Register("MyProperty", typeof(string), typeof(CreateAccountView));
public CreateAccountView() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
}
}
It seems to me that your current attempt is on the right track. The main issue with the code you posted is that the CreateAccountView.Button_Click() handler doesn't have access to the DataContext property it should be setting:
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
This DataContext property belongs to the CreateAccountView user control. However, this is not the controlling context for what's being displayed. So changing the value of that DataContext property doesn't have any useful effect. (Indeed, a user control should not set its own DataContext property at all, because doing so discards whatever context the client code using that user control had set.)
There's not enough context to know exactly what the best way for you to do this would be. I don't think it would be possible to provide enough context here on Stack Overflow. The overall architecture will depend on too many little details about your program. But, one way to approach this which I think is a good one would be this:
Create a "main" view model that governs the overall behavior of the app
Create individual view models that relate to different states of the UI
Have the main view model configure the individual view models to switch the current view model as appropriate, given the user input (e.g. clicking buttons)
Translating that into code, looks something like this…
First, the view models:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
Of course, these view models contain only the implementation details needed to handle the UI switching. In your program, each would also include the stuff specific to each view state that you need.
In my little sample, the "home" view contains a couple of buttons, used to select the individual sub-views available:
<UserControl x:Class="WpfApp1.HomeView"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
The sub views just contain the button required to go back to the home view:
<UserControl x:Class="WpfApp1.Sub1View"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
Finally, the main window sets the main view model, and declares templates to use for each of the specific sub views:
<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:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
Importantly, you'll see that none of the view objects include any code-behind. It's not necessary when you approach the problem this way, at least not for the purpose of controlling basic behaviors in the code. (You may still wind up with code-behind for view objects, but this will usually only be for the purpose of implementing specific user-interface behaviors unique to that view object, not for dealing with the view model state.)
Using this approach, you let WPF do as much of the heavy-lifting as possible. It also decouples all of the view model objects from each other. There's a clear hierarchy: only the top-level "main" view model even knows about the other view models. This allows the sub-view models ("home", "sub1", and "sub2") to be reused as necessary in other scenarios without any modification or special-case handling within them.
Here are the helper classes I used above:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}
I wrote code which should navigate between user controls in WPF application using MVVM, but I realised that this code doesn't work.
From window LoginView I want to change the view to VotingCardView.
Actually, after clicking on the button in the LoginView, the method DisplayVCV gets executed, but the view is not going to change. What am I doing wrong?
MainView.xaml:
<Window x:Class="ElectionCalculator.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:ElectionCalculator"
xmlns:v="clr-namespace:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
Title="Election calculator" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<ContentControl Content="{Binding ViewModel}" />
</Window>
LoginView.xaml:
<UserControl x:Class="ElectionCalculator.View.LoginView"
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:ElectionCalculator.View"
xmlns:vm="clr-namespace:ElectionCalculator.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button Command="{Binding DataContext.DisplayVC, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=OneWay}" Margin="161,147,47,124" />
</Grid>
</UserControl>
MainViewModel.cs
class MainViewModel : BaseViewModel
{
public BaseViewModel ViewModel { get; set; }
public MainViewModel()
{
ViewModel = new LoginViewModel();
}
public ICommand DisplayVC { get { return new RelayCommand(DisplayVCV); } }
public void DisplayVCV()
{
ViewModel = new VotingCardViewModel();
MessageBox.Show("DisplayVCCommandExecuted");
}
}
Your ViewModel property implementation doesn't raise a PropertyChanged event when the value changes. This is usually done via an INotifyPropertyChanged implementation. Because of that, your view doesn't get notified that something has changed.
In your case, this means that you need a backing field for your ViewModel property and implement your ViewModel property similar to this:
private BaseViewModel _viewModel;
public BaseViewModel ViewModel
{
get { return _viewModel; }
set
{
if(_viewModel != value)
{
_viewModel = value;
OnPropertyChanged("ViewModel");
}
}
}
Since you are already deriving from BaseViewModel I assume that the method OnPropertyChanged (or some method with a similar name) is implemented there. It is also quite common that you don't have to specify the property name ("ViewModel") as an argument, since lots of implementations use the [CallerMemberName] attribute for this purpose.
I'm trying to implement a dialer in WPF. I have a window, and inside it a user control. The user control has lots of buttons, but the user can also use the num pad to enter numbers.
I created a small sample project to show where I'm at:
MainWindow.xaml
<Window x:Class="wpf_dialer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf_dialer"
Title="MainWindow" Height="200" Width="525">
<Window.Resources>
<local:DialerViewModel x:Key="myViewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource myViewModel}">
<local:Dialer />
</Grid>
</Window>
Dialer.xaml
<UserControl x:Class="wpf_dialer.Dialer"
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:wpf_dialer"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="300"
d:DataContext="{d:DesignInstance local:DialerViewModel}"
Loaded="UserControl_Loaded">
<UserControl.InputBindings>
<KeyBinding Key="A" Command="{Binding CommandDialValue, Mode=OneTime}" CommandParameter="." />
<KeyBinding Key="Back" Command="{Binding CommandDialValue, Mode=OneTime}" CommandParameter="Back" />
<KeyBinding Key="Enter" Command="{Binding CommandAcceptValue, Mode=OneTime}" CommandParameter="KeyBinding" />
</UserControl.InputBindings>
<UniformGrid Columns="1">
<TextBox IsEnabled="False" Text="{Binding DialedValue, Mode=OneWay}" MinWidth="200" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"/>
<Button Content="OK" Margin="60,30" Command="{Binding CommandAcceptValue, Mode=OneTime}" CommandParameter="Button" />
</UniformGrid>
</UserControl>
DialerViewModel.cs
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace wpf_dialer
{
class DialerViewModel : INotifyPropertyChanged
{
private Random RAND = new Random();
private string _dialed_value = "00";
public string DialedValue
{
get { return _dialed_value; }
set
{
if (_dialed_value == value) return;
_dialed_value = value;
RaisePropertyChanged("DialedValue");
}
}
public ICommand CommandDialValue { get { return new CommandImpl(DialValue); } }
public ICommand CommandAcceptValue { get { return new CommandImpl(Alert); } }
private void DialValue(object parameter)
{
if (parameter.ToString() == "Back")
{
if (DialedValue.Length > 0)
{
DialedValue = DialedValue.Substring(0, DialedValue.Length - 1);
}
}
else
{
DialedValue += RAND.Next(0, 10).ToString();
}
}
private void Alert(object parameter)
{
System.Windows.MessageBox.Show(parameter.ToString());
}
#region INotifyPropertyChanged
protected void RaisePropertyChanged(string property_name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private class CommandImpl : ICommand
{
private readonly Action<object> _action = null;
public CommandImpl(Action<object> action)
{
_action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) { return true; }
public void Execute(object parameter) { _action(parameter); }
}
}
}
Objectives:
As soon as the window is loaded, when the user presses the A key, the CommandDialValue is executed;
When the user presses Enter, a message box is displayed with the text "KeyBinding". The CommandAcceptValue must be called from the KeyBinding, and NOT from the button;
Problems:
When the window is loaded, the KeyBindings don't execute. They are executed when I click a button somewhere in the UserControl;
When I press Enter, the button's command is executed, but I want the user control's KeyBinding to be executed;
This dialer must be held in a UserControl (or a ControlTemplate, or DataTemplate), because it's contained in a very elaborate window.
I don't want to put the KeyBindings on the Window, because then the UserControl is not reusable, and because its DataContext is not the same as the user control.
UPDATE:
I solved the second problem by setting Focusable="False" on all buttons.
To prevent the buttons from gaining focus, I set Focusable="False" for all buttons.
To set the focus when the window opens, I set Focusable="True" on the UserControl, and on the Loaded event I called Focus().
Dialer.xaml
d:DataContext="{d:DesignInstance local:DialerViewModel}"
Loaded="UserControl_Loaded" Focusable="True">
<UserControl.InputBindings>
Dialer.xaml.cs
private void UserControl_Loaded(object sender, RoutedEventArgs e) {
Focus();
}
I found no combination of FocusManager.FocusedElement that worked. I tried {Binding ElementName=myUserControl}, and {Binding RelativeSource={RelativeSource Self}}.
It's a question of Focus. When your window is loaded for the first time, your user control does not have Focus. So key bindings' gesture will not intercept your keypress. You have, at the first app loading time, to give Focus to your user control. (Focusable ="True"(i don't know if this helps but i am sure the FocusManager will helps)). Then your key gestures will work well.
I have a custom control, which has a button:
<UserControl x:Class="Gambit.Views.FileSelectionControl"
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"
SnapsToDevicePixels="True"
mc:Ignorable="d">
...
<Button Content="Load"
Margin="5,5,5,5"
Height="22"
Width="70"
IsDefault="True"
IsEnabled="{Binding SelectedFileExists}"
AttachedCommand:CommandBehavior.Event="Click"
AttachedCommand:CommandBehavior.Command="{Binding CloseDialogCommand}"/>
...
</UserControl>
I want to include this control, in another control, but I want to set the Load buttons visibility at design time in the host control; something like
<UserControl x:Class="Gambit.Views.SomeOtherControl"
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"
SnapsToDevicePixels="True"
mc:Ignorable="d">
...
<GroupBox Header="Select Test Data">
<Views:FileSelectionControl <Here Set the Load Button Visibility>/>
</GroupBox>
...
</UserControl>
where <Here Set the Load Button Visibility> shows where i want to set the visibility of the control. How is this done [without breaking the MVVM pattern]?
Thanks for your time.
You can create DependencyProperty in your UserControl:
public partial class SomeView : UserControl
{
...
public static DependencyProperty ButtonVisibilityProperty = DependencyProperty.Register("ButtonVisibility", typeof(Visibility), typeof(SomeView));
public Visibility ButtonVisibility
{
get { return (Visibility)GetValue(ButtonVisibilityProperty); }
set { SetValue(ButtonVisibilityProperty, value); }
}
}
bind it to Button.Visibility:
<UserControl x:Class="WpfApplication2.SomeView"
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"
mc:Ignorable="d">
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=ButtonVisibility}" Content="My Button"/>
</UserControl>
and then you can control Visibility from outside like so:
<local:SomeView ButtonVisibility="Collapsed"/>
and because it's a DependencyProperty you can use Binding as well
Hi just create a bool or Visibility Type property in UserControl1 and set it in Usercontrol2 like
UserControl1 xaml
<UserControl x:Class="WpfApplication4.UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Button x:Name="Loadbutton" Content="load"/>
</Grid>
xaml.cs
public UserControl1()
{
InitializeComponent();
}
bool showLoadButton;
public bool ShowLoadButton
{
get { return showLoadButton; }
set
{
showLoadButton = value;
if (showLoadButton)
Loadbutton.Visibility = Visibility.Visible;
else
Loadbutton.Visibility = Visibility.Collapsed;
}
}
UserControl2 Set ShowLoadButton True or false
<UserControl x:Class="WpfApplication4.UserControl2"
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:WpfApplication4"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<local:UserControl1 ShowLoadButton="True"/>
</Grid>
If you do not want to define a property in the UserControl, which you can always create attached dependency property, and you can declare it in a separate class under the common namespace.
Something like this:
MainWindow.xaml
<local:TestUserControl AttachedProperties:ButtonExt.Visibility="Visible" />
TestUserControl.xaml
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=(AttachedProperties:ButtonExt.Visibility)}"
Content="TestButton" />
Attached property definition:
public static class ButtonExt
{
public static readonly DependencyProperty VisibilityProperty;
public static void SetVisibility(DependencyObject DepObject, Visibility value)
{
DepObject.SetValue(VisibilityProperty, value);
}
public static Visibility GetVisibility(DependencyObject DepObject)
{
return (Visibility)DepObject.GetValue(VisibilityProperty);
}
static ButtonExt()
{
PropertyMetadata VisibiltyPropertyMetadata = new PropertyMetadata(Visibility.Collapsed);
VisibilityProperty = DependencyProperty.RegisterAttached("Visibility",
typeof(Visibility),
typeof(ButtonExt),
VisibiltyPropertyMetadata);
}
}
Some notes about code-behind in MVVM
I agree with #dkozl, his example does not violate the principle of MVVM, in some cases, the code is present in the View, for example (personally, I always try to avoid code-behind):
Installation DataContext.
The use of different patterns such as Mediator, Proxy, etc.
Determination of properties and behaviors that pertain only to View (as in your case).
The most important thing when you use the code-behind, it is that all actions possible through ViewModel occurred, ie in ViewModel contains all the logic and for example, in the View click event, call the function, which is in ViewModel.
For more information about code-behind, please see the answers to the recent question:
WPF MVVM Code Behind