Prism Custom Confirmation Interaction - wpf

I created a custom confirmation window in an app with Prism Unity, WPF & Mvvm. I need help with the notifications that need to be sent back to the viewmodel. I have this in the detail record view, let's call it MyDetailView.
<!-- Custom Confirmation Window -->
<ie:Interaction.Triggers>
<interactionRequest:InteractionRequestTrigger
SourceObject="{Binding ConfirmationRequest, Mode=TwoWay}">
<mycontrols:PopupWindowAction1 IsModal="True"/>
</interactionRequest:InteractionRequestTrigger>
</ie:Interaction.Triggers>
As shown above, I made the interaction Mode=TwoWay so that the confirmation popup window can send back the button click result for the OK or Cancel button. The confirmation window appears as it should, but I don't know how to send the button click result back to my viewmodel, say MyDetailViewModel. That is the main question.
EDIT: This is MyDetailViewMmodel method that raises the InteractionRequest.
private void RaiseConfirmation()
{ConfirmationRequest
.Raise(new Confirmation()
{
Title = "Confirmation Popup",
Content = "Save Changes?"
}, c =>{if (c.Confirmed)
{ UoW.AdrTypeRos.Submit();}
This is the PopupWindowAction1 class. Part of the answer to the question may be how do I implement the Notification and FinishedInteraction methods.
class PopupWindowAction1 : PopupWindowAction, IInteractionRequestAware
{
protected override Window GetWindow(INotification notification)
{ // custom metrowindow using mahapps
MetroWindow wrapperWindow = new ConfirmWindow1();
wrapperWindow.DataContext = notification;
wrapperWindow.Title = notification.Title;
this.PrepareContentForWindow(notification, wrapperWindow);
return wrapperWindow;
}
public INotification Notification
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public Action FinishInteraction
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
}
Is there some interaction I need to put in my ConfirmWindow1, something like this?
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonUp">
<ei:CallMethodAction
TargetObject="{Binding RelativeSource={RelativeSource AncestorType=UserControl},
Path=DataContext}"
MethodName="DataContext.ValidateConfirm"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Do I even need to do that type of interaction within the button? If so, how do I code it needs so that it corresponds to the particular viewmodel that invoked the interaction. Any suggestions? Thank you.

Main thing is, when you raise the interaction, provide a callback that is triggered when the interaction is finished. This callback gets the notification back and your interaction should have stored all potentially interesting return values there.
Here's an example...
Relevant parts of the ViewModel:
public InteractionRequest<SelectQuantityNotification> SelectQuantityRequest
{
get;
}
// in some handler that triggers the interaction
SelectQuantityRequest.Raise( new SelectQuantityNotification { Title = "Load how much stuff?", Maximum = maximumQuantity },
notification =>
{
if (notification.Confirmed)
_worldStateService.ExecuteCommand( new LoadCargoCommand( sourceStockpile.Stockpile, cartViewModel.Cart, notification.Quantity ) );
} );
... and from the View:
<i:Interaction.Triggers>
<interactionRequest:InteractionRequestTrigger
SourceObject="{Binding SelectQuantityRequest, Mode=OneWay}">
<framework:FixedSizePopupWindowAction>
<interactionRequest:PopupWindowAction.WindowContent>
<views:SelectSampleDataForImportPopup/>
</interactionRequest:PopupWindowAction.WindowContent>
</framework:FixedSizePopupWindowAction>
</interactionRequest:InteractionRequestTrigger>
</i:Interaction.Triggers>
Additionally, we need a class to hold the data that's passed around, and a ViewModel/View pair for the interaction itself.
Here's the data holding class (note that Maximum is passed to the interaction, and Quantity returned from it):
internal class SelectQuantityNotification : Confirmation
{
public int Maximum
{
get;
set;
}
public int Quantity
{
get;
set;
}
}
This is the View of the interaction popup:
<UserControl x:Class="ClientModule.Views.SelectSampleDataForImportPopup"
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:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Orientation="Vertical">
<TextBlock>
Amount: <Run Text="{Binding Quantity}"/>
</TextBlock>
<Slider Orientation="Horizontal" Minimum="0" Maximum="{Binding Maximum}" Value="{Binding Quantity}" TickPlacement="BottomRight"/>
<Button Content="Ok" Command="{Binding OkCommand}"/>
</StackPanel>
</UserControl>
and it's ViewModel:
internal class SelectSampleDataForImportPopupViewModel : BindableBase, IInteractionRequestAware
{
public SelectSampleDataForImportPopupViewModel()
{
OkCommand = new DelegateCommand( OnOk );
}
public DelegateCommand OkCommand
{
get;
}
public int Quantity
{
get { return _notification?.Quantity ?? 0; }
set
{
if (_notification == null)
return;
_notification.Quantity = value;
OnPropertyChanged( () => Quantity );
}
}
public int Maximum => _notification?.Maximum ?? 0;
#region IInteractionRequestAware
public INotification Notification
{
get { return _notification; }
set
{
SetProperty( ref _notification, value as SelectQuantityNotification );
OnPropertyChanged( () => Maximum );
OnPropertyChanged( () => Quantity );
}
}
public Action FinishInteraction
{
get;
set;
}
#endregion
#region private
private SelectQuantityNotification _notification;
private void OnOk()
{
if (_notification != null)
_notification.Confirmed = true;
FinishInteraction();
}
#endregion
}

Related

WPF MVVM Caliburn Displaying all ErrorMsgs in a specific ViewModel

I was thinking about displaying error messages to the user in a specific area of the shellview.
So I'm instantiating the ErrorViewModel in my ShellViewModel and it's the ErrorModel is displayed correct. The Textblock withing the ViewModel is displaying it's initialvalue as it should.
But if I pass a string via a public method from another ViewModel (e.g. LoginViewModel) the errorstring is passed to the ErrorViewModel and is also firing the NotifyOfPropertyChange(() => PublishErrorMsg) but the Textblock isnt changing.
Can't see why the content of the textblock won't change.
ErrorViewModel:
public class ErrorViewModel : Screen
{
private string publishErrorMsg;
public string PublishErrorMsg
{
get { return publishErrorMsg; }
set
{
publishErrorMsg = value;
NotifyOfPropertyChange(() => PublishErrorMsg);
}
}
public void ShowError(string msg) {
PublishErrorMsg = msg;
// MessageBox.Show(msg);
NotifyOfPropertyChange(() => PublishErrorMsg);
}
}
XAML ErrorView:
<UserControl x:Class="Views.ErrorView"
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:Views"
mc:Ignorable="d"
d:DesignHeight="40" d:DesignWidth="800" Background="White">
<Grid>
<DockPanel VerticalAlignment="Bottom" Height="40" Background="#FF474A57">
<TextBlock Text="{Binding PublishErrorMsg}"
Width="200" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FFE69393" FontSize="14">
</TextBlock>
</DockPanel>
</Grid>
Following the part which triggers the event:
public void LogIn() {
this.container.GetInstance<ErrorViewModel>().ShowError("User logged in");
try {
ResetPublishMsg();
ActiveUserModel.CreateIfAuthenticated(UserName, Password);
events.PublishOnUIThread(new LogOnEvent());
}
catch (Exception ex) {
this.container.GetInstance<ErrorViewModel>().PublishErrorMsg = ex.Message;
}
}
Thanks for help!
Maybe have you forgotten to subscribe to event ? see EventAggregator
public class ErrorViewModel : Screen, IHandle<LogOnEvent>
{
private readonly IEventAggregator _eventAggregator;
public ErrorViewModel(IEventAggregator eventAggregator) {
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public void Handle(LogOnEvent message) {
// Handle the message here.
}
}

Async progress bar in WPF using binding

I have a problem with visualizing the MVVM WPF progress bar. My methods for the main script runs but the progress bar isn't updating at all. I could use tips on getting in the right direction. In my previous codes, I was given tips to use Progress<T> (by #Jonathan Willcock) but I couldn't implement it successfully.
(Please note that this post is not a repeated question because last time, I used button click but I want it to purely be run on data binding)
Question
How do I asynchronously bind the progress bar to the method which is called in my view models? I am using Delegate command to call my methods and I do not want to use the button click event.
What I have in the view - XAML
I have two main functions here, the progress bar and the button. The button starts the method calling successfully but the progress bar doesn't load.
<ProgressBar Name="pbStatus"
Minimum="0"
Value="{Binding PrgBarVal, Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Maximum="100"
Height="20"/>
<Button x:Name = "ApplyButton" Margin="0 1 0 1" Content ="Run software" Command="{Binding RunCalcBtn, Mode=TwoWay}"/>
What I have in the XAML.cs
What I know from the background worker method is that it tries to run it async. I have an issue here, I added a breakpoint here to check if the code runs through the 'Window_ContentRendered' method but it doesn't.
public partial class UserInterface: UserControl
{
public UserInterface()
{
InitializeComponent();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork; //Do I need a worker.ProgressChanged here?
worker.RunWorkerAsync();
}
thenamespace.ViewModel.SelectedViewModel get = new thenamespace.ViewModel.SelectedViewModel ();
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = Convert.ToInt32(get.PrgBarVal); i < 100; i++)
{
(sender as BackgroundWorker).ReportProgress(i);
}
}
}
What I have in the View Model
In the view model, I am using delegate command to call my data binding methods.
The public SelectedViewModel() is where the Run() method is called. In the Run() method, the functions in there runs correctly and updates the value of PrgBarVal correctly. However, this doesn't update the progress bar at all.
public class SelectedViewModel : ModelView, INotifyPropertyChanged
{
public ModelView modelView { get; set; }
private DelegateCommand _runCalcBtn;
public DelegateCommand RunCalcBtn
{
get { return _runCalcBtn; }
set
{
_runCalcBtn = value;
SetPropertyChanged("RunCalcBtn"); //Same as RaisedPropertyChanged
}
}
public SelectedViewModel()
{
modelView = new ModelView();
RunCalcBtn = new DelegateCommand(Run);
}
private int _prgBarVal;
public int PrgBarVal
{
get { return _prgBarVal; }
set
{
_prgBarVal = value;
OnPropertyChanged("PrgBarVal"); //Same as RaisedPropertyChanged
}
}
//Main Function
private async void Run()
{
MethodToWork(); //Function that calls other methods to run
}
Thank you very much for your help!
Here is the ViewModel (used NuGet ReactiveUI.WPF)
public class MainViewModel : ReactiveObject
{
private int _workProgress;
public MainViewModel()
{
IProgress<int> progress = new Progress<int>( e => WorkProgress = e );
StartWork = ReactiveCommand.CreateFromTask( () => ExecuteStartWorkAsync( progress ) );
}
private async Task ExecuteStartWorkAsync( IProgress<int> progress )
{
progress.Report( 0 );
for ( int i = 0; i < 1000; i++ )
{
await Task.Delay( 10 ).ConfigureAwait( false );
progress.Report( (int)Math.Floor( i / 1000.0 * 100.0 ) );
}
}
public int WorkProgress { get => _workProgress; private set => this.RaiseAndSetIfChanged( ref _workProgress, value ); }
public ICommand StartWork { get; }
}
and the View
<Window x:Class="WpfApp4.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:WpfApp4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="Start work" Command="{Binding StartWork}" Width="80"/>
<ProgressBar Value="{Binding WorkProgress,Mode=OneWay}" Width="120"/>
</StackPanel>
</Grid>
</Window>

WPF: Changing tabs makes Windowsformshost child disappear

if you can spare the time, I am working on a problem for which I can't find a solution on the internet.
I need two tabs' richtextboxes to bind the same property. Both RichtextBoxes are hosted in WPF via Windowsformshost. But if I alternate between tabs, one RichtTextBox will simply dissapear (always the first one that was visible). I am migrating an app and so far, I am forced to use the Windowsforms RichtextBox.
I hope I managed to properly convey my problem - sorry, I am not a native speaker.
Thanks in advance
Edit:
I was asked to provide a clear example of my problem. Thanks for the note. I completely rewrote my question. Further, I have uploaded a micro app where I have isolated the problem. Just click the two tab buttons alternately and one Richtextbox will dissapear.
Below, I will provide the code if this serves:
This is my Mainwindow (XAML):
<StackPanel Orientation="Horizontal" Height="35" Margin="0,35,0,0" VerticalAlignment="Top" >
<Button x:Name="Tab1" Command="{Binding LeftCommand}" Content="Left" MinWidth="100" />
<Button x:Name="Tab2" Command="{Binding RightCommand}" Content="Right" MinWidth="100" />
</StackPanel>
<Frame x:Name="MyFrame"
Content="{Binding Path=CurrentTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="5,70,0,0" NavigationUIVisibility="Hidden" />
This is its viewmodel:
class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand LeftCommand { get; }
public ICommand RightCommand { get; }
private TabViewModel MyTabViewModel { get; set; }
private PageLeft MyPageLeft { get; set; }
private PageRight MyPageRight { get; set; }
public MainWindowViewModel()
{
this.LeftCommand = new ModelCommand(p => this.SetSelectedTab("left"));
this.RightCommand = new ModelCommand(p => this.SetSelectedTab("right"));
this.MyTabViewModel = new TabViewModel();
this.MyPageLeft = new PageLeft() { DataContext = this.MyTabViewModel };
this.MyPageRight = new PageRight() { DataContext = this.MyTabViewModel };
//initial view on something
//this.SetSelectedTab("left");
}
private void SetSelectedTab(string param)
{
switch (param)
{
case "left":
this.CurrentTab = this.MyPageLeft;
break;
case "right":
this.CurrentTab = this.MyPageRight;
break;
}
}
private object _CurrentTab;
public object CurrentTab
{
get { return _CurrentTab; }
set
{
if (value != _CurrentTab)
{
_CurrentTab = value;
NotifyPropertyChanged_MainViewModel();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged_MainViewModel([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Furthermore, I have two pages (MyPageLeft, MyPageRight) that use the same viewmodel (TabViewModel) and use the same bit of XAML code:
<ContentControl Content="{Binding Path=MyWindowsFormsHost, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Both Pages use the same TabViewModel:
class TabViewModel : INotifyPropertyChanged
{
private WindowsFormsHost _MyWindowsFormsHost;
public WindowsFormsHost MyWindowsFormsHost
{
get { return _MyWindowsFormsHost; }
set
{
if (value != _MyWindowsFormsHost)
{
_MyWindowsFormsHost = value;
NotifyPropertyChanged_TabViewModel();
}
}
}
public TabViewModel()
{
this.MyWindowsFormsHost = new WindowsFormsHost() { Child = new RichTextBox() { Text = "test" } };
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged_TabViewModel([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The Problem: If I start the app and click on the two tab buttons alternatingly, one of the framed RichtextBoxes will dissapear.
If anyone might need it, I used a dirty solution - although it might not be recommendable.
I extended the event of the switch tab buttons. It takes the RTF property of the currently selected Tab's Richtextbox and infuses it in the other Richtextbox. It goes kinda like this:
if (Tab2 Button is clicked)
this.MyRTF = Tab1.Richtextbox.RTF;
Tab2.Richttextbox.Rtf = this.MyRTF;
Note that this is a beginner's hack on a probably overall questionable approach.
Thanks to anyone who read my question!

How do I Bind WPF Commands between a UserControl and a parent Window

I'l start by letting a picture do some talking.
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
Many thanks for any insights,
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
First, our Window
<Window x:Class="UCsAndICommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:UCsAndICommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<t:ViewModel />
</Window.DataContext>
<t:ItemsEditor Items="{Binding Items}"
AddItem="{Binding AddItem}"
RemoveItem="{Binding RemoveItem}" />
</Window>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
Next, the UserControl
<UserControl x:Class="UCsAndICommands.ItemsEditor"
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:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.
Here's the definitions of these properties on the UC
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
public class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public ICommand RemoveItem { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
AddItem = new DelegatedCommand<object>(
o => true, o => Items.Add(new Item()));
RemoveItem = new DelegatedCommand<Item>(
i => true, i => Items.Remove(i));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
Refer the below code.
UserControl.XAML
<Grid>
<ListBox ItemsSource="{Binding Things}" x:Name="lst">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ThingName}" Margin="3"/>
<Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Window.Xaml
<Window x:Class="MultiBind_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBind_Learning"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
<local:UserControl2/>
</StackPanel>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
class ThingViewModel
{
private ObservableCollection<Thing> things = new ObservableCollection<Thing>();
public ObservableCollection<Thing> Things
{
get { return things; }
set { things = value; }
}
public ICommand AddCommnd { get; set; }
public ICommand RemoveCommand { get; set; }
public ThingViewModel()
{
for (int i = 0; i < 10; i++)
{
things.Add(new Thing() { ThingName="Thing" +i});
}
AddCommnd = new BaseCommand(Add);
RemoveCommand = new BaseCommand(Remove);
}
void Add(object obj)
{
things.Add(new Thing() {ThingName="Added New" });
}
void Remove(object obj)
{
things.Remove((Thing)obj);
}
}
Thing.cs
class Thing :INotifyPropertyChanged
{
private string thingName;
public string ThingName
{
get { return thingName; }
set { thingName = value; OnPropertyChanged("ThingName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.

Binding doesn't update when property is set inside a command

I am having a surprising difficulty trying to make a simple thing work, that is, setting a property in a method called by a Command bound to a Button.
When I set the property in the ViewModel constructor, the correct value is properly displayed in View, but when I set this property with the command's method, the View doesn't update, although any breakpoint I create is reached (even inside RaisePropertyChanged in my ViewModelBase). I am using vanilla RelayCommand found easily in online tutorials (from Josh Smith if I am not mistaken).
My project can be downloaded here (Dropbox);
Some important code blocks are below:
ViewModel:
public class IdiomaViewModel : ViewModelBase
{
public String Idioma {
get { return _idioma; }
set {
_idioma = value;
RaisePropertyChanged(() => Idioma);
}
}
String _idioma;
public IdiomaViewModel() {
Idioma = "nenhum";
}
public void Portugues () {
Idioma = "portu";
}
private bool PodePortugues()
{
if (true) // <-- incluir teste aqui!!!
return true;
return false;
}
RelayCommand _comando_portugues;
public ICommand ComandoPortugues {
get {
if (_comando_portugues == null) {
_comando_portugues = new RelayCommand(param => Portugues(),
param => PodePortugues());
}
return _comando_portugues;
}
}
public void Ingles () {
Idioma = "ingle";
}
private bool PodeIngles()
{
if (true) // <-- incluir teste aqui!!!
return true;
return false;
}
RelayCommand _comando_ingles;
public ICommand ComandoIngles {
get {
if (_comando_ingles == null) {
_comando_ingles = new RelayCommand(param => Ingles(),
param => PodeIngles());
}
return _comando_ingles;
}
}
}
View with no extra code behind:
<Window x:Class="TemQueFuncionar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:TemQueFuncionar"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<app:IdiomaViewModel/>
</Window.DataContext>
<StackPanel>
<Button Content="Ingles" Command="{Binding ComandoIngles, Mode=OneWay}"/>
<Button Content="Portugues" Command="{Binding ComandoPortugues, Mode=OneWay}"/>
<Label Content="{Binding Idioma}"/>
</StackPanel>
</Window>
Youdid fill the Interface implementation put you did not mention it to the base view model.
You are missing this : INotifyPropertyChanged linking Interface to the base class, this makes the the View refreshes the content.
You missed the statement ViewModelBase:INotifyPropertyChanged on ViewModelBase

Resources