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...
Related
I am (new in C# and WPF and) trying to bind data from more sources (class properties) and I am a bit confused from different guides and advices. I want to dynamically add Users in userList and showing for example the last insert and whole list at the same time. That is done on different place in source code, but simple like in contructor of my example. How and where should I set binding and datacontext for those three elements (myName,myTitle and myUserList) to reflect changes in main class properties? Should I call every time function for update binding target, or set this.datacontext after editing properties? Or should I bind to some function (if it's possible) which returns the value I need? I am a bit lost with binding to property of object and datacontexts etc. Here is an example from what I have:
<Window x:Name="Window" x:Class="WpfTest.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:WpfTest">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBox x:Name="myName" Text="" Grid.Column="1"/>
<TextBox x:Name="myTitle" Text="" Grid.Column="0"/>
</StackPanel>
<ListBox x:Name="myUserList">
</ListBox>
</Grid>
</Window>
And
public partial class MainWindow : Window {
public User myUser;
public Dictionary<int,User> userList= new Dictionary<int, User>();
public object SubWindow { get; private set; }
public MainWindow() {
newUser = new User();
newUser.Title = "John Doe";
newUser.Name = "Dr.";
this.userList.Add(index,newUser);
this.myUser=newUser;
InitializeComponent();
}
}
And
public class User
{
public String Name { get; set; }
public String Title { get; set; }
}
Thanks for any advice.
First thing is first, WPF works best when you are working with MVVM, the general idea is implementing the INotifyPropertyChanged, what ever items you add to you change, will propagate to the framework and update your views.
When working with Lists, use an ObservableCollection. If you want to add items dynamically to it, you would need to modify the ObservableCollection instead. For best results, in your UserControl, use a DataTemplate for the specific type to display a formatted version of your values.
For the second part, showing the last added item, there are a few ways you can go about this, the best would be to add a new Items(Grid, Stackpanel, etc) that can hold data, use Binding to set its value to a the same context as your list(i.e the ObservableCollection) and create a Converter that will use the ObservableCollection as input, inside your specific converter implementation, just get the last item added and Display it to the control you want( you can use a dataTemplate for this as well)
Solution: You have to bind input data in model class(User) and use model to insert data in the listbox like below
<Window x:Class="WpfRegistration.Listbox"
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:WpfRegistration"
mc:Ignorable="d"
Title="Listbox" Height="450" Width="800">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.15*"></RowDefinition>
<RowDefinition Height="0.85*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="63*"></ColumnDefinition>
<ColumnDefinition Width="26*"></ColumnDefinition>
<ColumnDefinition Width="109*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBox x:Name="myName" Text="" Grid.Column="1"/>
<TextBox x:Name="myTitle" Text="" Grid.Column="0"/>
</StackPanel>
<StackPanel>
<Button Height="23" Margin="50,40,0,0" Name="button1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="76" Click="Button1_OnClick" >Add Item</Button>
</StackPanel>
<StackPanel>
<Button Height="23" Margin="140,40,10,12" Name="DeleteButton" VerticalAlignment="Top" Click="DeleteButton_Click">Delete Item</Button>
</StackPanel>
<ListBox Grid.Row="1" Grid.Column="0" BorderThickness="3" BorderBrush="Black" Margin="0,60,0,100" x:Name="myUserList">
</ListBox>
</Grid>
</Grid>
Xaml.cs
public partial class Listbox : Window
{
public Listbox()
{
InitializeComponent();
User newUser = new User();
newUser.Title = "John Doe";
newUser.Name = "Dr.";
this.myUserList.Items.Add(newUser.Title + newUser.Name);
}
private void Button1_OnClick(object sender, RoutedEventArgs e)
{
User newUser = new User();
newUser.Title = myTitle.Text;
newUser.Name = myName.Text;
myUserList.Items.Add(newUser.Name + " " + newUser.Title );
myTitle.Text=String.Empty;
myName.Text=String.Empty;
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
myUserList.Items.RemoveAt(myUserList.Items.IndexOf(myUserList.SelectedItem));
}
}
public class User
{
public string name;
public string title;
public String Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("name");
}
}
public string Title
{
get { return title; }
set
{
title = value;
OnPropertyChanged("title");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I am an experienced developer, but relative newcomer to the world of WPF and MVVM. I’ve been reading up on various tutorials and examples of following the MVVM pattern. I am working on converting an existing MDI Windows forms (a student/class management system) application into WPF. My basic design is for a menu (tree view) docked on the left side of the main window with a tab control that would contain the different views (student, class, teacher, billing etc) that the user requires. As proof of concept (and to get my head around WPF) I have the following:
A simple model, Student
public class Student
{
public DateTime BirthDate { get; set; }
public string Forename { get; set; }
public int Id { get; set; }
public string Surname { get; set; }
public override string ToString()
{
return String.Format("{0}, {1}", Surname, Forename);
}
}
The StudentViewModel
public class StudentViewModel : WorkspaceViewModel
{
private Student student;
public override string DisplayName
{
get
{
return String.Format("{0} {1}", student.Forename, student.Surname);
}
}
public string Forename
{
get
{
return student.Forename;
}
set
{
student.Forename = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public int Id
{
get
{
return student.Id;
}
set
{
student.Id = value;
RaisePropertyChanged();
}
}
public string Surname
{
get
{
return student.Surname;
}
set
{
student.Surname = value;
RaisePropertyChanged();
RaisePropertyChanged("DisplayName");
}
}
public StudentViewModel()
{
this.student = new Student();
}
public StudentViewModel(Student student)
{
this.student = student;
}
}
The view model inherits WorkspaceViewModel, an abstract class
public abstract class WorkspaceViewModel : ViewModelBase
{
public RelayCommand CloseCommand { get; set; }
public event EventHandler OnClose;
public WorkspaceViewModel()
{
CloseCommand = new RelayCommand(Close);
}
private void Close()
{
OnClose?.Invoke(this, EventArgs.Empty);
}
}
This in turn inherits ViewModelBase, where I implement INotifyPropertyChanged. The RelayCommand class is a standard implementation of the ICommand interface.
The MainWindowViewModel holds a collection of Workspaces
public class MainViewModel : WorkspaceViewModel
{
private WorkspaceViewModel workspace;
private ObservableCollection<WorkspaceViewModel> workspaces;
public WorkspaceViewModel Workspace
{
get
{
return workspace;
}
set
{
workspace = value;
RaisePropertyChanged();
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
return workspaces;
}
set
{
workspaces = value;
RaisePropertyChanged();
}
}
public RelayCommand NewTabCommand { get; set; }
public MainViewModel()
{
Workspaces = new ObservableCollection<WorkspaceViewModel>();
Workspaces.CollectionChanged += Workspaces_CollectionChanged;
NewTabCommand = new RelayCommand(NewTab);
}
private void NewTab()
{
Student student = new Student();
StudentViewModel workspace = new StudentViewModel(student);
Workspaces.Add(workspace);
Workspace = workspace;
}
private void Workspaces_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.NewItems)
{
workspace.OnClose += Workspace_OnClose; ;
}
}
if (e.OldItems != null && e.OldItems.Count != 0)
{
foreach (WorkspaceViewModel workspace in e.OldItems)
{
workspace.OnClose -= Workspace_OnClose;
}
}
}
private void Workspace_OnClose(object sender, EventArgs e)
{
var workspace = (WorkspaceViewModel)sender;
Workspaces.Remove(workspace);
}
}
The StudentView xaml
<UserControl x:Class="MvvmTest.View.StudentView"
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:MvvmTest.View"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="ID:"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Forename:"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Surname:"/>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Date of Birth:"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Id, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding Forename, Mode=TwoWay}"/>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Surname, Mode=TwoWay}"/>
<DatePicker Grid.Column="1" Grid.Row="3" SelectedDate="{Binding BirthDate, Mode=TwoWay}"/>
</Grid>
</UserControl>
The StudentViewModel and StudentView are linked via a resource dictionary in App.xaml
<ResourceDictionary>
<vm:MainViewModel x:Key="MainViewModel"/>
<DataTemplate DataType="{x:Type vm:StudentViewModel}">
<v:StudentView/>
</DataTemplate>
</ResourceDictionary>
And finally, the MainWindow view (goal is that this will eventually conform to MVVM in that the MainWindowViewModel will define the menu structure)
<Window x:Class="MvvmTest.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:MvvmTest"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:vm="clr-namespace:MvvmTest.ViewModel"
xmlns:v="clr-namespace:MvvmTest.View"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Vertical">
<Button Content="New Student">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding NewTabCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
<TabControl ItemsSource="{Binding Workspaces}" SelectedItem="{Binding Workspace}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"/>
<Button>X</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<UserControl Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
When I click the ‘New student’ button a new student workspace is created, added to Workspaces collection and displays in the TabControl. All seems well. But when I enter data on the view I noticed that the tab header isn’t updated. First sign that all is not working as it should...
Then when I click ‘New student’ a second time. Another workspace is created, but that duplicates the values entered in the first. Further, when editting the second tab, the first is also updated.
Placing a breakpoint into the NewTab method revealed that although the Workspaces collection holds StudentViewModels, the display properties are still null; even though the StudentView appears to hold data.
After much puzzling I discovered that if I do not set the data context on the StudentView xaml then the binding behaves properly and the test app works as expected. But then doesn't that mean the xaml designer isn't really validating the display property bindings, even though at runtime the path is resolved?
Anyway, I’m now left a few questions. How and why does what I've done work? It essentially appears to go against everything I’ve read and seen on MVVM. Furthermore when trying to apply this application to a MVVM framework (eg MVVM Light) the views are explicitly defined with the data context set in the xaml (eg: DataContext="{Binding Path=Student, Source={StaticResource Locator}}). Which makes even less sense...
As I said, what I’ve got does work, but I’m not really understanding why, and therefore doubt is clawing away that I’ve done something wrong. As a result I’m reluctant to proceed further on serious development from fear of having to rework later (having dug myself into a hole).
Child controls automatically inherit DataContext from their parent. So if no DataContext is specified in the UserControl then each instance uses the instance of StudentViewModel contained in the WorkSpaces Collection. On the other hand when specifing the datacontext in the UserControl XAML each instance of the view is bound the same ViewModel instance. That is why changing data on one view results in changes on all other views. The views are all referencing the same object. I hope that is clear.
I have a WPF MVVM app, which gets its data from a user setting which is an ObservableCollection of type Copyable (a custom class) called Copyables. Within the main view model (ClipboardAssistantViewModel), I set the source of a CollectionViewSource to Copyables. This is then bound to an ItemsControl in the main view (MainWindow). The DataTemplate for this ItemsControl is a user control, 'CopyableControl', which is essentially a button, but with attached properties that allow me to bind data and commands to it.
When a user clicks on a CopyableControl, a view model (DefineCopyableViewModel) is added to an ObservableCollection of those in ClipboardAssistantViewModel, and that collection is bound to an ItemsControl in MainWindow. The DataTemplate of this is a UserControl called DefineCopyableControl, which is set up in such a way that the current values associated with the clicked Copyable are bound to textboxes in the DefineCopyableControl for editing.
My problem: There is a method in DefineCopyableViewModel, EditCopyable(), which only works on the first run (its job is to save the user settings once any edits have taken place and the user clicks "OK"). If I click the CopyableControl and make an edit, then click "OK", then click it again, make another edit, then click "OK", then close the application and open it again, only the first edit has been saved (even though the UI was updated with the edited value both times). It seems to have something to do with the data-binding need to be "refreshed"; please see the comments in this method in the code for my findings around this.
My code is as follows:
Model:
namespace ClipboardAssistant.Models
{
public class Copyable : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
private string textToCopy;
public string TextToCopy
{
get { return textToCopy; }
set
{
if (value != textToCopy)
{
textToCopy = value;
NotifyPropertyChanged("TextToCopy");
}
}
}
public Copyable() { }
public Copyable(string Name, string TextToCopy)
{
this.Name = Name;
this.TextToCopy = TextToCopy;
}
}
}
ViewModels:
namespace ClipboardAssistant.ViewModels
{
public class ClipboardAssistantViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public CollectionViewSource CopyablesView { get; set; }
public ObservableCollection<DefineCopyableViewModel> Definers { get; set; }
public CopyableClickCommand CopyableClickCommand { get; set; }
public ClipboardAssistantViewModel()
{
Definers = new ObservableCollection<DefineCopyableViewModel>();
CopyablesView = new CollectionViewSource();
CopyablesView.Source = Properties.Settings.Default.Copyables;
CopyableClickCommand = new CopyableClickCommand(this);
EditModeClickCommand = new EditModeClickCommand(this);
}
public void RefreshCopyables()
{
// Both these methods of refreshing appear to have the same effect.
Properties.Settings.Default.Copyables = (ObservableCollection<Copyable>)CopyablesView.Source;
CopyablesView.Source = Properties.Settings.Default.Copyables;
}
public void EditCopyable(Copyable Copyable)
{
Definers.Add(new DefineCopyableViewModel(Copyable, this));
}
}
}
namespace ClipboardAssistant.ViewModels
{
public class DefineCopyableViewModel : INotifyPropertyChanged
{
// INotifyPropertyChanged Implementation
public ClipboardAssistantViewModel MyParent { get; set; }
public Copyable Copyable { get; set; }
public DefinerOKClickCommand DefinerOKClickCommand { get; set; }
public DefineCopyableViewModel(Copyable Copyable, ClipboardAssistantViewModel MyParent)
{
this.Copyable = Copyable;
this.MyParent = MyParent;
DefinerOKClickCommand = new DefinerOKClickCommand(this);
}
public void EditCopyable()
{
// Refresh, save, no refresh, save -> doesn't save second edit.
// Save, refresh, save, no refresh -> does save second edit.
MessageBoxResult r = MessageBox.Show("Refresh?", "Refresh", MessageBoxButton.YesNo);
if (r == MessageBoxResult.Yes)
{
MyParent.RefreshCopyables();
}
// These two MessageBox methods (save and refresh) can be swapped around (see above comments).
MessageBoxResult s = MessageBox.Show("Save?", "Save", MessageBoxButton.YesNo);
if (s == MessageBoxResult.Yes)
{
Properties.Settings.Default.Save();
}
MyParent.Definers.Remove(this);
}
}
}
MainWindow:
<Window x:Class="ClipboardAssistant.Views.MainWindow" x:Name="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:vm="clr-namespace:ClipboardAssistant.ViewModels"
xmlns:ctrls="clr-namespace:ClipboardAssistant.Controls"
mc:Ignorable="d"
Title="Clipboard Assistant" Height="400" Width="700">
<Window.DataContext>
<vm:ClipboardAssistantViewModel />
</Window.DataContext>
<Grid>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding CopyablesView.View}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:CopyableControl Copyable="{Binding}"
ClickCopyable="{Binding DataContext.CopyableClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<DockPanel Grid.Row="2">
<Button x:Name="btnEditCopyableMode" HorizontalAlignment="Left" DockPanel.Dock="Left"
Content="Edit" Margin="0,0,10,0" Command="{Binding EditModeClickCommand}" />
</DockPanel>
</Grid>
<ItemsControl ItemsSource="{Binding Definers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ctrls:DefineCopyableControl Copyable="{Binding DataContext.Copyable}"
ClickCancel="{Binding DataContext.DefinerCancelClickCommand, ElementName=mainWindow}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
CopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.CopyableControl" x:Name="copyableControl"
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:ClipboardAssistant.Controls"
mc:Ignorable="d" d:DesignHeight="75" d:DesignWidth="200">
<Grid Width="200" Height="75">
<Button Command="{Binding ClickCopyable, ElementName=copyableControl}"
CommandParameter="{Binding Copyable, ElementName=copyableControl}"
Content="{Binding Copyable.Name, ElementName=copyableControl}"
Style="{StaticResource CopyableMainButtonStyle}" />
</Grid>
</UserControl>
namespace ClipboardAssistant.Controls
{
public partial class CopyableControl : UserControl
{
public static readonly DependencyProperty ClickCopyableProperty =
DependencyProperty.Register("ClickCopyable", typeof(ICommand), typeof(CopyableControl));
public ICommand ClickCopyable
{
get { return (ICommand)GetValue(ClickCopyableProperty); }
set { SetValue(ClickCopyableProperty, value); }
}
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(CopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public CopyableControl()
{
InitializeComponent();
}
}
}
DefineCopyableControl:
<UserControl x:Class="ClipboardAssistant.Controls.DefineCopyableControl" x:Name="defineCopyableControl"
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="500">
<Grid x:Name="MainGrid" Background="Blue">
<Grid Width="200" Height="180">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="10" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Name" Foreground="White" />
<TextBox Grid.Row="1" Text="{Binding Copyable.Name}" x:Name="tbN" />
<Label Grid.Row="3" Content="Copyable Text" Foreground="White" />
<TextBox Grid.Row="4" Text="{Binding Copyable.TextToCopy}" x:Name="tbTTC" />
<DockPanel Grid.Row="6">
<Button Width="70" Content="OK" DockPanel.Dock="Right" HorizontalAlignment="Right"
Command="{Binding DefinerOKClickCommand}"
CommandParameter="{Binding ElementName=defineCopyableControl}" />
</DockPanel>
</Grid>
</Grid>
</UserControl>
public partial class DefineCopyableControl : UserControl
{
public static readonly DependencyProperty CopyableProperty =
DependencyProperty.Register("Copyable", typeof(Copyable), typeof(DefineCopyableControl));
public Copyable Copyable
{
get { return (Copyable)GetValue(CopyableProperty); }
set { SetValue(CopyableProperty, value); }
}
public DefineCopyableControl()
{
InitializeComponent();
}
}
Commands:
namespace ClipboardAssistant.ViewModels.Commands
{
public class CopyableClickCommand : ICommand
{
public ClipboardAssistantViewModel ViewModel { get; set; }
public CopyableClickCommand(ClipboardAssistantViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Copyable cp = (Copyable)parameter;
ViewModel.EditCopyable(cp);
}
}
}
namespace ClipboardAssistant.ViewModels.Commands
{
public class DefinerOKClickCommand : ICommand
{
public DefineCopyableViewModel ViewModel { get; set; }
public DefinerOKClickCommand(DefineCopyableViewModel viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.EditCopyable();
}
}
}
Settings:
namespace ClipboardAssistant.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable> Copyables {
get {
return ((global::System.Collections.ObjectModel.ObservableCollection<ClipboardAssistant.Models.Copyable>)(this["Copyables"]));
}
set {
this["Copyables"] = value;
}
}
}
}
I'm assuming you are using Visual Studio. In that case, in the My Project do you have the settings listed in the settings tab?
I ran into the same issue a while back where I tried to programatically create/save/update settings and was unsucessful until I created the setting in the Settings tab. Once that was complete I was able to make my saves as necessary.
The you just use
MySettings.Default.SettingName = value
MySettings.Default.Save()
Hope this helps!
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); }
}
}
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.