User Control locked when one bound command is disabled - c#

In my application, I have quite a few grids so I was working on creating a user control that has all of the possible buttons we would use with a grid (Add, Remove, Insert, Move Up, Move Down, etc). I use dependency properties to set the visibility and state (enabled or disabled) of the buttons and I use routed events for the click events.
I'm using Caliburn.Micro in my project so I'm using Message.Attach to specify multiple events and the actions they are attached to. On their own, these work without issue.
The problem I have is when the actions have Can* properties to go with them. So for example, I have Add and Remove methods and CanAdd and CanRemove properties to set their state. If CanAdd or CanRemove is false (and the other is true), both buttons get disabled (probably the whole user control is getting disabled).
I can get around this issue by using properties for the states that don't conform to the Caliburn.Micro naming (ex. CanAddRecord and CanRemoveRecord instead of CanAdd and CanRemove) so it doesn't automatically use them and then set the binding in each of the dependency properties. I would prefer not to do this but will if it's in the only way.
I created a simple example project to show this. The project was written in VS 2015 and uses .NET 4.5.2. I'm using Caliburn.Micro v3.1.0.
The application has a main window with the user control on the top. The user control has Add and Remove buttons. Under the user control is a label that shows a count. The Add button increments the count and the Remove button decrements the count. The Add button is always enabled. The Remove button is only enabled if the count is greater than 0 (so you can't set the count to a negative). The count starts at 1 so both buttons start enabled. If you click Add, both buttons stay enabled. If you click Remove till the count gets to 0, both buttons end up disabled.
Is this by design because the messages are attached to the user control as opposed to the specific buttons? Is there a way around this?
I originally tried putting the Message.Attach statements inside of the user control but when clicking the buttons it seemed to want to bind to Add and Remove methods inside of the user control.
App.xaml
<Application
x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<local:AppBootstrapper x:Key="Bootstrapper" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
AppBootStrapper.cs
using System.Windows;
using Caliburn.Micro;
namespace WpfApplication1
{
public class AppBootstrapper : BootstrapperBase
{
public AppBootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainViewModel>();
}
}
}
GridButtonBar.xaml
<UserControl
x:Class="WpfApplication1.GridButtonBar"
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="40" d:DesignWidth="230">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
x:Name="AddButton"
Content="Add"
Grid.Column="0"
Margin="3"
IsEnabled="{Binding AddButtonIsEnabled}"
Click="Add_OnClick" />
<Button
x:Name="RemoveButton"
Content="Remove"
Grid.Column="1"
Margin="3"
IsEnabled="{Binding RemoveButtonIsEnabled}"
Click="Remove_OnClick" />
</Grid>
</UserControl>
GridButtonBar.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class GridButtonBar : UserControl
{
public static readonly DependencyProperty AddButtonIsEnabledProperty =
DependencyProperty.Register(nameof(AddButtonIsEnabled), typeof(bool), typeof(GridButtonBar),
new UIPropertyMetadata(true));
public static readonly DependencyProperty RemoveButtonIsEnabledProperty =
DependencyProperty.Register(nameof(RemoveButtonIsEnabled), typeof(bool), typeof(GridButtonBar),
new UIPropertyMetadata(true));
public static readonly RoutedEvent AddButtonClickedEvent = EventManager.RegisterRoutedEvent("AddButtonClicked",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(GridButtonBar));
public static readonly RoutedEvent RemoveButtonClickedEvent =
EventManager.RegisterRoutedEvent("RemoveButtonClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler),
typeof(GridButtonBar));
public GridButtonBar()
{
InitializeComponent();
}
public bool AddButtonIsEnabled
{
get { return (bool) GetValue(AddButtonIsEnabledProperty); }
set { SetValue(AddButtonIsEnabledProperty, value); }
}
public bool RemoveButtonIsEnabled
{
get { return (bool) GetValue(RemoveButtonIsEnabledProperty); }
set { SetValue(RemoveButtonIsEnabledProperty, value); }
}
public event RoutedEventHandler AddButtonClicked
{
add { AddHandler(AddButtonClickedEvent, value); }
remove { RemoveHandler(AddButtonClickedEvent, value); }
}
public event RoutedEventHandler RemoveButtonClicked
{
add { AddHandler(RemoveButtonClickedEvent, value); }
remove { RemoveHandler(RemoveButtonClickedEvent, value); }
}
private void Add_OnClick(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(AddButtonClickedEvent));
}
private void Remove_OnClick(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(RemoveButtonClickedEvent));
}
}
}
MainView.xaml
<Window
x:Class="WpfApplication1.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:WpfApplication1"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
Title="{Binding WindowTitle}"
WindowStyle="SingleBorderWindow"
ResizeMode="NoResize"
Height="140" Width="250">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:GridButtonBar
Grid.Row="0"
cal:Message.Attach="[Event AddButtonClicked] = [Action Add];[Event RemoveButtonClicked] = [Action Remove]" />
<Label
Grid.Row="1"
Margin="5"
BorderBrush="Black"
BorderThickness="1"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{Binding Count}" />
</Grid>
</Window>
MainViewModel.cs
using Caliburn.Micro;
namespace WpfApplication1
{
public class MainViewModel : PropertyChangedBase
{
private const string WindowTitleDefault = "Caliburn Micro Test";
private int _count = 1;
private string _windowTitle = WindowTitleDefault;
public string WindowTitle
{
get { return _windowTitle; }
set
{
_windowTitle = value;
NotifyOfPropertyChange(() => WindowTitle);
}
}
public int Count
{
get { return _count; }
set
{
_count = value;
NotifyOfPropertyChange(() => Count);
}
}
public bool CanAdd => true;
public bool CanRemove => Count > 0;
public void Add()
{
Count++;
}
public void Remove()
{
Count--;
NotifyOfPropertyChange(() => CanRemove);
}
}
}

Related

Prism WPF Binding RegionManager.RegionName

I have a Prism 7 application with two modules, called ModuleA and ModuleB. In my main window I would like to be able to show either ModuleA if "Show A" button is clicked or ModuleB if "Show B" button is clicked. I implemented the behaviour the following way:
MainWindow.xaml
<Window x:Class="ModulesTest.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl prism:RegionManager.RegionName="{Binding Module}" />
<StackPanel Grid.Row="1">
<Button Command="{Binding ShowModuleCommand}"
CommandParameter="A">
Show A
</Button>
<Button Command="{Binding ShowModuleCommand}"
CommandParameter="B">
Show B
</Button>
</StackPanel>
</Grid>
MainWindowViewModel.cs
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string fieldName;
public string Module
{
get { return fieldName; }
set { SetProperty(ref fieldName, value); }
}
private DelegateCommand<string> _showModuleCommand;
public DelegateCommand<string> ShowModuleCommand =>
_showModuleCommand ?? (_showModuleCommand = new DelegateCommand<string>(ExecuteShowModuleCommand));
void ExecuteShowModuleCommand(string module)
{
Module = "Module" + module;
}
public MainWindowViewModel()
{
Module = "ModuleA";
}
}
The problem is that the RegionManager.RegionName remains "ModuleA" as set in the constructor of the ViewModel and doesn't change when "Show B" is clicked. Is the binding of the RegionManager.RegionName not allowed by design or am I doing it wrong?
Here's also the link to the repo: https://github.com/moisejbraver/ModulesTest
A region is a structural part of your user interface. You should not reassign the region name once it has been assigned to a specific control.
If you need to navigate inside a region, consider using the IRegionNavigationService...

How can I assign DataGrids to a user control in WPF at the point of use?

I am trying to define a user control for the typical dual list situation (where there are two lists of items side by side and button controls to cause selected items from one to be transferred to the other). I am not very proficient at WPF -- most of what I've learned has been bits and pieces through sites like this. I have learned that I can create custom dependency properties for the control so that I can defer binding of items in the control (buttons, textboxes, etc.) until the control is actually used which is great. However, for my control I am going to have the two lists (probably DataGrids since most of my code, to date, has involved them) but they will require a lot more than binding so what I would like to do is something like this:
<MyUserControl . . . .>
<DataGrid . . . .>
<DataGrid . . . .>
</MyUserControl>
But I have no idea how to make that work. I thought there might be some way I could use ContentControls as a stand-in for the DataGrids and then somehow link the datagrids back to the contentcontrols in the usercontrol but I don't really understand Contentcontrols and none of the examples I found using them seemed to apply at all to what I want to do.
Can anyone point me in the right direction on this? Thank you.
I did some more research and found a promising approach, here:
How to add controls dynamically to a UserControl through user's XAML?
I did a small proof-of-concept project and it worked great so I thought I would share it here. It uses Galasoft's MVVM Light framework.
I created a user control with a textblock, a ContentControl, and a button:
<UserControl x:Class="DataGridInUserControlDemo.UserControls.DGPlusUC"
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:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
x:Name="ControlRoot">
<Grid DataContext="{Binding ElementName=ControlRoot}" Margin="10, 10, 10, 10" MinHeight="300" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=BannerText}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
<ContentControl Grid.Row="1" Content="{Binding Path=InnerContent}" />
<Button Grid.Row="2" x:Name="DemoButton" Content="{Binding Path=ButtonContent}" Command="{Binding Path=ButtonCommand}" Width="75" Height="25" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
The attributes I wish to bind external to the UserControl are themselves bound to custom DependencyProperty(s).
This is the code-behind for the user control containing the DependencyProperty definitions:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace DataGridInUserControlDemo.UserControls
{
public partial class DGPlusUC : UserControl
{
public DGPlusUC()
{
InitializeComponent();
}
public const string BannerTextPropertyName = "BannerText";
public string BannerText
{
get
{
return (string)GetValue(BannerTextProperty);
}
set
{
SetValue(BannerTextProperty, value);
}
}
public static readonly DependencyProperty BannerTextProperty = DependencyProperty.Register(
BannerTextPropertyName,
typeof(string),
typeof(DGPlusUC));
public const string ButtonContentPropertyName = "ButtonContent";
public string ButtonContent
{
get
{
return (string)GetValue(ButtonContentProperty);
}
set
{
SetValue(ButtonContentProperty, value);
}
}
public static readonly DependencyProperty ButtonContentProperty = DependencyProperty.Register(
ButtonContentPropertyName,
typeof(string),
typeof(DGPlusUC));
public const string ButtonCommandPropertyName = "ButtonCommand";
public ICommand ButtonCommand
{
get
{
return (ICommand)GetValue(ButtonCommandProperty);
}
set
{
SetValue(ButtonCommandProperty, value);
}
}
public static readonly DependencyProperty ButtonCommandProperty = DependencyProperty.Register(
ButtonCommandPropertyName,
typeof(ICommand),
typeof(DGPlusUC));
public const string InnerContentPropertyName = "InnerContent";
public UIElement InnerContent
{
get
{
return (UIElement)GetValue(InnerContentProperty);
}
set
{
SetValue(InnerContentProperty, value);
}
}
public static readonly DependencyProperty InnerContentProperty = DependencyProperty.Register(
InnerContentPropertyName,
typeof(UIElement),
typeof(DGPlusUC));
}
}
This is the XAML for the main window:
<Window x:Class="DataGridInUserControlDemo.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:ignore="http://www.galasoft.ch/ignore"
xmlns:demo="clr-namespace:DataGridInUserControlDemo.UserControls"
mc:Ignorable="d ignore"
Height="400"
Width="400"
Title="MVVM Light Application"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<demo:DGPlusUC BannerText="{Binding UCTitle}" ButtonContent="{Binding ButtonText}" ButtonCommand="{Binding ButtonCommand}" HorizontalAlignment="Center" VerticalAlignment="Center">
<demo:DGPlusUC.InnerContent>
<Grid DataContext="{Binding Main, Source={StaticResource Locator}}">
<DataGrid ItemsSource="{Binding Path=DataItems}" AutoGenerateColumns="True" />
</Grid>
</demo:DGPlusUC.InnerContent>
</demo:DGPlusUC>
</Grid>
</Window>
The custom DependencyProperty(s) are used as tags in the control to bind to the properties in the ViewModel.
Note the bracketed demo:DGPlusUC.InnerContent -- this is where the replacement code for the ContentControl goes. The embedded UserControl inherits the Window's datacontext but for some reason this section did not, so, after experimenting with it a bit, I just threw up my hands and declared the DataContext explicitly.
And here is the ViewModel code:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using DataGridInUserControlDemo.Model;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace DataGridInUserControlDemo.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly IDataModel theModel;
private ObservableCollection<DataItem> _DataItems;
public ObservableCollection<DataItem> DataItems
{
get
{
return _DataItems;
}
set
{
if (_DataItems == value)
{
return;
}
var oldValue = _DataItems;
_DataItems = value;
RaisePropertyChanged(() => DataItems, oldValue, value, true);
}
}
private string _ButtonText = "First";
public string ButtonText
{
get
{
return this._ButtonText;
}
set
{
if (this._ButtonText == value)
{
return;
}
var oldValue = this._ButtonText;
this._ButtonText = value;
RaisePropertyChanged(() => ButtonText, oldValue, value, true);
}
}
private string _UCTitle = string.Empty;
public string UCTitle
{
get
{
return this._UCTitle;
}
set
{
if (this._UCTitle == value)
{
return;
}
var oldValue = this._UCTitle;
this._UCTitle = value;
RaisePropertyChanged(() => UCTitle, oldValue, value, true);
}
}
private ICommand _ButtonCommand;
public ICommand ButtonCommand
{
get
{
return this._ButtonCommand;
}
set
{
if (this._ButtonCommand == value)
{
return;
}
var oldValue = this._ButtonCommand;
this._ButtonCommand = value;
RaisePropertyChanged(() => ButtonCommand, oldValue, value, true);
}
}
public MainViewModel(IDataModel model)
{
this.theModel = model;
this._UCTitle = "DataGrid in User Control Demo";
this._DataItems = new ObservableCollection<DataItem>(this.theModel.SomeData);
this._ButtonCommand = new RelayCommand(this.ButtonCmd, () => { return true; }) ;
}
private void ButtonCmd()
{
if (this.ButtonText == "First")
{
this.ButtonText = "Second";
}
else
{
this.ButtonText = "First";
}
}
}
}
Finally, here are the results:
DataGrid in UserControl Demo

Change Enabled State of Button immediately on any change in a TextBox?

I use data binding and command binding to set the enabled state of a button, depending on whether a particular string property has a value or not. Or you might say, I have a mandatory TextBox, and I want the user to not be able to click Ok before at least 1 character has been entered.
My code does exactly that, only that the enabled state of the button is not updated before the TextBox is unfocused, e.g. by pressing the Tab key. I want this to happen immediately, on any change of the TextBox content. How can I achieve this? Without breaking out of MVVM, of course!
View:
<Window x:Class="Gebietsmanager.GebietBearbeitenDlg.View"
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:Gebietsmanager.GebietBearbeitenDlg"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:ViewModel}"
Title="Gebiet bearbeiten" Height="110" Width="300" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" ResizeMode="NoResize">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Margin="8,8,0,0">Name:</Label>
<TextBox Grid.Column="1" Text="{Binding Name}" Margin="8,8,8,0"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="8,8,0,0">
<Button IsDefault="True" Command="{Binding Commit}">Ok</Button>
<Button Command="{Binding Rollback}" Margin="8,0,0,0">Reset</Button>
<Button IsCancel="True" Margin="8,0,0,0">Cancel</Button>
</StackPanel>
</Grid>
</Window>
ViewModel:
using System.ComponentModel;
namespace Gebietsmanager.GebietBearbeitenDlg
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel(Gebiet gebiet)
{
_gebiet = gebiet;
_gebietCopy = new Gebiet();
Helpers.CopyPropValues(_gebietCopy, gebiet);
Commit = new Command(
() => Helpers.CopyPropValues(_gebiet, _gebietCopy),
() => !string.IsNullOrEmpty(_gebietCopy.Name));
Rollback = new Command(DoRollback);
}
private readonly Gebiet _gebiet;
private readonly Gebiet _gebietCopy;
private void DoRollback()
{
Helpers.CopyPropValues(_gebietCopy, _gebiet);
OnPropertyChanged();
}
public string Name
{
get { return _gebietCopy.Name; }
set
{
if (_gebietCopy.Name != value)
{
_gebietCopy.Name = value;
OnPropertyChanged(nameof(Name));
}
}
}
public Command Commit { get; private set; }
public Command Rollback { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Command implementation:
using System;
using System.Windows.Input;
namespace Gebietsmanager
{
public sealed class Command : ICommand
{
public Command(Action executeAction, Func<bool> canExecutePredicate = null)
{
_executeAction = executeAction;
_canExecutePredicate = canExecutePredicate;
}
private readonly Action _executeAction;
private readonly Func<bool> _canExecutePredicate;
public void Execute(object parameter)
{
_executeAction?.Invoke();
}
public bool CanExecute(object parameter)
{
return _canExecutePredicate?.Invoke() ?? true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
You need to set UpdateSourceTrigger=PropertyChanged in your binding,
Example with MVVMLight:
XAML
<Window x:Class="WpfApplication2.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:local="clr-namespace:WpfApplication2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<Grid>
<StackPanel>
<TextBlock Text="Name" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Go !" IsEnabled="{Binding IsReady}" />
</StackPanel>
</Grid>
</Window>
Code
internal class MyModel : ViewModelBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
Set(() => Name, ref _name, value);
RaisePropertyChanged(() => IsReady);
}
}
public bool IsReady
{
get { return !string.IsNullOrEmpty(Name); }
}
}

ICommand doesn't work

I am starting with MVVM pattern and I have a problem with my button's command. I have a window which contains a TextBox for login and a custom PasswordBox with Password DependencyProperty for password (I know that any password should not be kept in memory, however it is only for fun, so do not be frustrated :) ) . There is also a button and I want it to be enabled if and only if the login and password are both not empty. However my command does not work properly. Here is the code:
LoginWindow xaml:
<Window x:Class="WpfMVVMApplication1.LoginWindow"
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:WpfMVVMApplication1"
xmlns:vm="clr-namespace:WpfMVVMApplication1.ViewModel"
mc:Ignorable="d"
Title="Login" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
Width="250" Height="150">
<Window.DataContext>
<vm:LoginViewModel />
</Window.DataContext>
<Window.Resources>
<!-- here some styles for controls -->
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Login:"/>
<TextBlock Text="Password:" Grid.Row="1"/>
<TextBox Text="{Binding Login, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<local:MyPasswordBox Grid.Row="1" PasswordText="{Binding Password, Mode=TwoWay}"/>
<Button Command="{Binding SaveUserCommand}"/>
</Grid>
My LoginViewModel class
public class LoginViewModel : INotifyPropertyChanged
{
public LoginViewModel()
{
SaveUserCommand = new Command(x => SaveUser(), x => { return !string.IsNullOrEmpty(Login) && !string.IsNullOrEmpty(Password); });
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand SaveUserCommand { get; private set; }
private string login;
private string password;
public string Login
{
get
{
return login;
}
set
{
login = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Login"));
}
}
public string Password
{
get
{
return password;
}
set
{
password = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Password"));
}
}
public void SaveUser() { }
}
Also MyPasswordBox class may be useful:
<UserControl x:Class="WpfMVVMApplication1.MyPasswordBox"
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:WpfMVVMApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<PasswordBox x:Name="password" PasswordChanged="password_PasswordChanged"/>
</Grid>
public partial class MyPasswordBox : UserControl
{
public MyPasswordBox()
{
InitializeComponent();
PasswordText = "";
}
public static readonly DependencyProperty PasswordTextProperty =
DependencyProperty.Register("PasswordText", typeof(string), typeof(MyPasswordBox), new FrameworkPropertyMetadata(""));
public string PasswordText
{
get { return (string)GetValue(PasswordTextProperty); }
set { SetValue(PasswordTextProperty, value); }
}
private void password_PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordText = password.Password;
}
}
I wrote some unit tests for that which checks the result of SaveUserCommand CanExecute method and they all passed. Furthermore, I run the debug in Visual Studio adding breakpoints in setters of Login and Password properties and they both are set properly.
I have run out of ideas what I could make wrong. Can anybody help me?
I feel that I don't notify the command about the changes properly, however I do not know how to do that
In order for WPF to pick up a change to whether a command can be executed, the command's CanExecuteChanged event needs to be fired.
You will need to fire this event when the login or the password changes. You haven't shown the source of your class Command, but it will need to have a method that fires its CanExecuteChanged event. Then, just call this method from the Login and Password property setters.

Relations between UserControl and MainWindow

How could I make possible to access data/properties from a UserControl and the parent, MainWindow (and viceversa)? For example, say I have a TextBox in my MainWindow called mainTextBox. Then I create a UserControl with another TextBox called ucTextBox. I also have a button called ucButton in the UserControl that should popup a MessageBox with the product of the values mainTextBox.Text * ucTextBox.Text (converted to double, to make it work).
What I really want to know is how to achieve to do this dynamically, with a button that allows to create more UserControls that are capable to interact with the parent. In this case it makes no sense to name every UserControl.
I've tried several things, mainly with get,set properties but with no desired outcome.
I'm not sure if I need to use UserControl, but it seems to, I've read that CustomControl is for a deep customization but I don't need that.
Here is just a quick sample to get you started (and what probably was meant by mr. #Adriano):
RootViewModel.cs:
public class RootViewModel :INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate {};
private void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private double _x;
private double _y;
public double X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged("X");
}
}
public double Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged("Y");
}
}
public double XY
{
get { return _x * _y; }
}
}
UserControl1.xaml:
<UserControl x:Class="WpfApplication2.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:DesignWidth="200">
<Grid>
<GroupBox Header="User Control">
<StackPanel>
<Label Content="Y:" />
<TextBox Text="{Binding Path=Y, UpdateSourceTrigger=PropertyChanged, FallbackValue=1}" Margin="5" />
<Button Content="Press me" Click="OnButtonClick" />
</StackPanel>
</GroupBox>
</Grid>
UserControl1.xaml.cs:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var viewModel = (RootViewModel)DataContext;
var resultMessage = string.Format("{0} * {1} = {2}", viewModel.X, viewModel.Y, viewModel.XY);
MessageBox.Show(resultMessage, "X * Y");
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication21="clr-namespace:WpfApplication2"
Title="Main Window" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel>
<Label Content="X:" />
<TextBox Text="{Binding Path=X, UpdateSourceTrigger=PropertyChanged, FallbackValue=1}" Margin="5" Height="24" />
</StackPanel>
<WpfApplication21:UserControl1 Grid.Row="1" Margin="5" />
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new RootViewModel
{
X = 5,
Y = 7
};
}
}

Categories