I'm new to MVVM and WPF and have been completely hung up on this issue for some time now. I'm trying to display a View (UserControl) based on the SelectedItem in a DataGrid. The UserControl renders with the data set in the constructor, but never updates.
I would really appreciate some insight from someone with experience in this. I tried adding a Mediator via setUpdateCallback and now the first row in the datagrid updates with the values of the other rows I click on, but this obviously isn't what I want, I need those updates to happen in the separate client view outside of the datagrid.
ClientPanel.xaml
<UserControl x:Class="B2BNet.View.ClientPanel"
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:VM="clr-namespace:B2BNet.ViewModel"
xmlns:V="clr-namespace:B2BNet.View"
xmlns:local="clr-namespace:B2BNet"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d">
<Grid>
<Grid.DataContext>
<VM:ClientPanel/>
</Grid.DataContext>
<TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="43" Width="280" Text="{Binding Title}" FontSize="36" FontFamily="Global Monospace"/>
<DataGrid AutoGenerateColumns="False" x:Name="dataGrid" HorizontalAlignment="Left" Margin="10,60,0,10" VerticalAlignment="Top" ItemsSource="{Binding Clients}" SelectedItem="{Binding currentClient}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding name}"></DataGridTextColumn>
<DataGridTextColumn Header="Active" Binding="{Binding active}"></DataGridTextColumn>
<DataGridTextColumn Header="Status" Binding="{Binding status}"></DataGridTextColumn>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding clientSelectionChanged_command}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<V:Client HorizontalAlignment="Left" Margin="163,58,-140,0" VerticalAlignment="Top" Content="{Binding currentClient}" Height="97" Width="201"/>
</Grid>
Client.xaml
<UserControl x:Class="B2BNet.View.Client"
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:VM="clr-namespace:B2BNet.ViewModel"
xmlns:local="clr-namespace:B2BNet"
mc:Ignorable="d">
<Grid>
<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>
<Label x:Name="name" Content="Name:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="active" Content="Active:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="9,41,0,0"/>
<Label x:Name="status" Content="Status:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,72,0,0"/>
<Label x:Name="namevalue" HorizontalAlignment="Left" Margin="59,10,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding name}"/>
<Label x:Name="activevalue" HorizontalAlignment="Left" Margin="59,41,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding active}"/>
<Label x:Name="statusvalue" HorizontalAlignment="Left" Margin="60,67,-1,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding status}"/>
</Grid>
ViewModel - ClientPanel.cs
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using B2BNet.MVVM;
namespace B2BNet.ViewModel
{
public class ClientPanel : ObservableObject
{
public string Title { get; set; }
private Client _currentClient;
public Client currentClient
{
get
{
return _currentClient;
}
set
{
_currentClient = value;
RaisePropertyChangedEvent( "currentClient" );
Utility.print("currentClient Changed: " + _currentClient.name);
Mediator.Instance.Notify(ViewModelMessages.UpdateClientViews, currentClient);
}
}
private ObservableCollection<Client> _Clients;
public ObservableCollection<Client> Clients
{
get
{
return _Clients;
}
set
{
_Clients = value;
RaisePropertyChangedEvent("Clients");
Utility.print("Clients Changed");
}
}
public ClientPanel()
{
Title = "Clients";
Clients = new ObservableCollection<Client>();
for ( int i = 0; i < 10; i++ )
{
Clients.Add( new Client { name = "Client-" + i, status = "Current", active = true } );
}
currentClient = Clients.First();
currentClient.setUpdateCallback();
}
////////////
//COMMANDS//
////////////
public ICommand clientSelectionChanged_command
{
get { return new DelegateCommand( clientSelectionChanged ); }
}
private void clientSelectionChanged( object parameter )
{
B2BNet.Utility.print("clientSelectionChanged");
}
}
}
ViewModel - Client.cs
using System;
using B2BNet.MVVM;
namespace B2BNet.ViewModel
{
public class Client : ObservableObject
{
private String _name;
public String name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChangedEvent( "name" );
Utility.print("Client.name changed: " + value );
}
}
private Boolean _active;
public Boolean active
{
get
{
return _active;
}
set
{
_active = value;
RaisePropertyChangedEvent( "active" );
Utility.print("Client.active changed" + value );
}
}
private String _status;
public String status
{
get
{
return _status;
}
set
{
_status = value;
RaisePropertyChangedEvent( "status" );
Utility.print("Client.status changed" + value );
}
}
public Client()
{
name = "Set in Client Constuctor";
status = "Set in Client Constructor";
}
public void setUpdateCallback()
{
////////////
//Mediator//
////////////
//Register a callback with the Mediator for Client
Mediator.Instance.Register(
(Object o) =>
{
Client client = (Client)o;
name = client.name;
active = client.active;
status = client.status;
}, B2BNet.MVVM.ViewModelMessages.UpdateClientViews
);
}
}
}
Very Basic MVVM Framework
ObservableObject.cs
using System.ComponentModel;
namespace B2BNet.MVVM
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent( string propertyName )
{
var handler = PropertyChanged;
if ( handler != null )
handler( this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
Mediator.cs
using System;
using B2BNet.lib;
namespace B2BNet.MVVM
{
/// <summary>
/// Available cross ViewModel messages
/// </summary>
public enum ViewModelMessages { UpdateClientViews = 1,
DebugUpdated = 2
};
public sealed class Mediator
{
#region Data
static readonly Mediator instance = new Mediator();
private volatile object locker = new object();
MultiDictionary<ViewModelMessages, Action<Object>> internalList
= new MultiDictionary<ViewModelMessages, Action<Object>>();
#endregion
#region Ctor
//CTORs
static Mediator()
{
}
private Mediator()
{
}
#endregion
#region Public Properties
/// <summary>
/// The singleton instance
/// </summary>
public static Mediator Instance
{
get
{
return instance;
}
}
#endregion
#region Public Methods
/// <summary>
/// Registers a callback to a specific message
/// </summary>
/// <param name="callback">The callback to use
/// when the message it seen</param>
/// <param name="message">The message to
/// register to</param>
public void Register(Action<Object> callback,
ViewModelMessages message)
{
internalList.AddValue(message, callback);
}
/// <summary>
/// Notify all callbacks that are registed to the specific message
/// </summary>
/// <param name="message">The message for the notify by</param>
/// <param name="args">The arguments for the message</param>
public void Notify(ViewModelMessages message,
object args)
{
if (internalList.ContainsKey(message))
{
//forward the message to all listeners
foreach (Action<object> callback in
internalList[message])
callback(args);
}
}
#endregion
}
}
DelegateCommand.cs
using System;
using System.Windows.Input;
namespace B2BNet.MVVM
{
public class DelegateCommand : ICommand
{
private readonly Action<object> _action;
public DelegateCommand( Action<object> action )
{
_action = action;
}
public void Execute( object parameter )
{
_action( parameter );
}
public bool CanExecute( object parameter )
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore 67
}
}
THE SOLUTION
The solution was a simple one. Thanks for the quick response Rachel :). I simple removed the following from each of the Views:
<!--<Grid.DataContext>
<VM:ClientPanel/>
</Grid.DataContext>-->
<!--<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>-->
and it works perfectly, even allowing me to update the datagrid: