Master View Details with MVVM Pattern - c#

I have a MVVM - WPF Browser Application application using Entity Framework 6.
I have tried to select a supplierDataGrid row and display the Products related in another datagrid. But it doesn't work. The application freezes when I select a row. Thanks for your help!
ViewModelBase
public class CommandBase<T> : INotifyPropertyChanged
{
#region "INotifyPropertyChanged members"
public event PropertyChangedEventHandler PropertyChanged;
//This routine is called each time a property value has been set.
//This will //cause an event to notify WPF via data-binding that a change has occurred.
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ObservableCollection<T> collection;
public ObservableCollection<T> Collection
{
get
{
if (collection == null)
{
Get();
}
return collection;
}
set { collection = value;
OnPropertyChanged("Collection"); }
}
private T _selected;
public T Selected
{
get {
if(_selected != null)
GetSub();
return _selected;
}
set { _selected = value;
OnPropertyChanged("Selected"); }
}
private ICommand getCommand;
private ICommand saveCommand;
private ICommand removeCommand;
private ICommand getSubCommand;
protected virtual void Save()
{
//return true;
}
protected virtual bool CanSave()
{
return true;
}
public ICommand DeleteCommand
{
get
{
// return removeCommand ?? (removeCommand = new RelayCommand(Delete,CanDelete));
return removeCommand ?? (removeCommand = new RelayCommand(p => this.Delete(), p => this.CanDelete()));
}
}
protected virtual void Delete()
{ }
protected virtual bool CanDelete()
{
if (Selected != null)
return true;
else
return false;
}
public ICommand GetSubCommand
{
get
{
return getSubCommand ?? (getSubCommand = new RelayCommand(p => this.GetSub(), p => this.CanGetSub()));
}
}
protected virtual async Task GetSub()
{
await Task.Delay(0);
}
protected virtual bool CanGetSub()
{
return true;
}
}
ViewModel
public class SupplierViewModel : CommandBase<foodSupplier>
{
public Context ctx = new Context();
protected override void Get()
{
ctx.foodSuppliers.ToList().ForEach(supplier => ctx.foodSuppliers.Local.Add(supplier));
Collection = ctx.foodSuppliers.Local;
}
protected override bool CanGet()
{
return true;
}
protected override void Save()
{
foreach (foodSupplier item in Collection)
{
if (ctx.Entry(item).State == System.Data.Entity.EntityState.Added)
{
ctx.foodSuppliers.Add(item);
}
}
ctx.SaveChanges();
}
protected override void Delete()
{
var id = Selected;
var supp = (from s in ctx.foodSuppliers
where s.idfoodSupplier == id.idfoodSupplier
select s).SingleOrDefault();
ctx.foodSuppliers.Remove(supp);
ctx.SaveChanges();
Collection.Remove(supp);
}
protected virtual bool CanDelete()
{
return true;
}
protected override async Task GetSub()
{
var supplier = Selected;
var pro = await (from p in ctx.products
where p.supplier == supplier.idfoodSupplier
select p).ToListAsync();
Products = pro;
}
private IList<product> _products;
public IList<product> Products
{
get
{
return _products;
}
set
{
_products = value;
OnPropertyChanged("Products");
}
}
}
View
<DataGrid x:Name="dataGrid"
Margin="5"
ItemsSource="{Binding Collection}"
AutoGenerateColumns="False"
SelectedItem="{Binding Selected, Mode=TwoWay}"
SelectionMode="Extended"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn x:Name="dataGridTextColumn"
Header="Supplier"
Binding="{Binding idfoodSupplier, UpdateSourceTrigger=PropertyChanged}"
Visibility="Hidden" />
<DataGridTextColumn Header="Supplier"
Binding="{Binding supplier, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding GetCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<Button Height="Auto"
Width="Auto"
Content="Delete"
Command="{Binding DeleteCommand}" />

EDIT: the program freezes, because you have infinite recursion. Selected getter call GetSub, it calls getter of Selected it call GetSub, etc...
I recommend you to call GetSub in Selected setter, but some additional refactoring may be needed.
TIP: when somethig freezes, pause the program in visual studio and take a look at callstack window. You would immediatelly know whats going on in this case. Maybe you will need to switch to Main thread in Threads window (Ctrl+Alt+H) after you press pause button.

Related

"Long" delay causes my view's button not to re-enable

I have a listview and a button in each column. When the user clicks a button, it triggers an asynchronous action in the viewmodel where I disable all buttons and do a big action. Once the action is completed, I re-enable them.
If the action takes too long, though, the buttons don't automatically get re-enabled, even though I'm setting the bound property to true and am notifying the view. If the user does ANY GUI action after the action is complete, the buttons will re-enable.
The other weird thing: If I do an await Task.Delay instead of doing Thread.Sleep (NB: I'm doing real work in the full application), it works correctly.
What's going on here?
I've simplified the code here by eliminating the model (all logic lives in the VM).
View-model code:
namespace WpfTestApp
{
public class viewmodel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public RelayAsyncCommand<object> RunCommand { get; private set; }
private ObservableCollection<subVM> _subVMs;
public ObservableCollection<subVM> SubVMs
{
get => _subVMs; set
{
_subVMs = value;
NotifyPropertyChanged();
}
}
public viewmodel()
{
RunCommand = new RelayAsyncCommand<object>(OnRun);
SubVMs = new ObservableCollection<subVM>
{
new subVM("ItemA"),
new subVM("ItemB"),
};
}
private async void OnRun(object o)
{
subVM vm = o as subVM;
if (vm != null)
{
ChangeRunMode(false);
Thread.Sleep(500);
}
ChangeRunMode(true);
}
private void ChangeRunMode(bool on)
{
foreach (subVM vm in SubVMs)
{
vm.ButtonEnabled = on;
}
}
}
public class subVM : INotifyPropertyChanged
{
private string name = "";
public string Name
{
get => name;
set
{
if (value != name)
{
name = value;
}
}
}
public subVM(string name)
{
Name = name;
}
private bool tsk = true;
public bool ButtonEnabled
{
get => tsk;
set
{
if (tsk != value)
{
tsk = value;
NotifyPropertyChanged("ButtonEnabled");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View XAML:
<Window x:Class="WpfTestApp.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:WpfTestApp"
mc:Ignorable="d"
Title="MainWindow" Height="130" Width="350">
<Window.DataContext>
<local:viewmodel/>
</Window.DataContext>
<ListView Margin="5"
BorderBrush="DarkSlateGray" BorderThickness="1"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding SubVMs}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
Width="200" DisplayMemberBinding ="{Binding Name}"/>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button
Content="Load"
IsEnabled="{Binding ButtonEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Margin="0"
VerticalAlignment="Center"
Command="{Binding Path=DataContext.RunCommand, IsAsync=True, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Window>
Things I've tried:
I tried await Task.Run on my actual problem, and it still happens.
RelayCommand and the Async version (I thought these were standard boilerplate, but here you go):
public class RelayAsyncCommand<T> : RelayCommand<T>
{
private bool isExecuting = false;
public event EventHandler Started;
public event EventHandler Ended;
public bool IsExecuting
{
get { return this.isExecuting; }
}
public RelayAsyncCommand(Action<T> execute, Predicate<T> canExecute)
: base(execute, canExecute)
{
}
public RelayAsyncCommand(Action<T> execute)
: base(execute)
{
}
public override Boolean CanExecute(Object parameter)
{
return ((base.CanExecute(parameter)) && (!this.isExecuting));
}
public override void Execute(object parameter)
{
try
{
this.isExecuting = true;
if (this.Started != null)
{
this.Started(this, EventArgs.Empty);
}
Task task = Task.Factory.StartNew(() =>
{
this._execute((T)parameter);
});
task.ContinueWith(t =>
{
this.OnRunWorkerCompleted(EventArgs.Empty);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
this.OnRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void OnRunWorkerCompleted(EventArgs e)
{
this.isExecuting = false;
if (this.Ended != null)
{
this.Ended(this, e);
}
}
}
public class RelayCommand<T> : ICommand
{
#region Fields
readonly protected Action<T> _execute;
readonly protected Predicate<T> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public virtual bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public virtual void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion // ICommand Members
}
inside the OnRun method you are actually locking the UI thread, preventing any UI update/refresh; you should await for the long operation (and that's exactly what you noticed doing await Task.Delay):
private async void OnRun(object o)
{
subVM vm = o as subVM;
if (vm != null)
{
ChangeRunMode(false);
await Task.Run(() =>
{
//put here your long operation as per your example
for (int i = 0; i < 500; i++)
{
for (int k = 0; k < 100000; k++) ;
}
});
}
ChangeRunMode(true);
}
indeed if you take a closer look to your window, you'll see that currently everything is freezed during the long operation execution, not just only the buttons being disabled.
I figured out my problem:
The button deactivating isn't coming from my binding when I'm running asynchronously. It's coming from the RelayAsyncCommand's CanExecute. CanExecute here returns false while the task is running, but we don't trigger a requery ever when it's done.
Easily fixed by adding a private set to the IsExecuting property, which calls the invalidate/requery function on change (just like the standard notifypropertychanged pattern). For posterity, here's the full fixed RelayAsyncCommand:
public class RelayAsyncCommand<T> : RelayCommand<T>
{
private bool _isExecuting = false;
public event EventHandler Started;
public event EventHandler Ended;
public bool IsExecuting
{
get { return _isExecuting; }
private set
{
if (value != _isExecuting)
{
_isExecuting = value;
CommandManager.InvalidateRequerySuggested();
}
}
}
public RelayAsyncCommand(Action<T> execute, Predicate<T> canExecute)
: base(execute, canExecute)
{
}
public RelayAsyncCommand(Action<T> execute)
: base(execute)
{
}
public override bool CanExecute(object parameter)
{
return ((base.CanExecute(parameter)) && (!IsExecuting));
}
public override void Execute(object parameter)
{
try
{
IsExecuting = true;
Started?.Invoke(this, EventArgs.Empty);
Task task = Task.Factory.StartNew(() =>
{
_execute((T)parameter);
});
task.ContinueWith(t =>
{
OnRunWorkerCompleted(EventArgs.Empty);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception ex)
{
OnRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void OnRunWorkerCompleted(EventArgs e)
{
IsExecuting = false;
Ended?.Invoke(this, e);
}
}
Thanks Peter for making me actually look at (what I thought was) boilerplate code.

WPF MVVM - Simple Binding Button with Textbox (Icommand)

What I'm trying to do is: when the textbox contains the value "123" it should enable the button and allow me to click it.
Solution Image &
View Image
I can not find a way to trigger the Button Command (Class called SpecialCommand.cs) based on my Button parameters. Could you support where I'm getting this MVVM pattern wrong?
WPF View [MainWindow.xaml]:
<Window.Resources>
<ViewModel:MainWindowVM x:Key="WindowVm"></ViewModel:MainWindowVM>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="123"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding SpecialCommand, Source={StaticResource WindowVm}}" CommandParameter="{Binding Text, ElementName=textBox, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</StackPanel>
</Grid>
ViewModel [MainWindowVM.cs]:
public class MainWindowVM
{
private SpecialCommand _specialCommand;
public SpecialCommand SpecialCommand { get => _specialCommand; set => _specialCommand = value; }
public MainWindowVM()
{
_specialCommand = new SpecialCommand();
}
}
Command [SpecialCommand.cs]
public class SpecialCommand : ICommand
{
public bool CanExecute(object parameter)
{
if (parameter != null && (parameter as string) == "123")
return true;
return false;
}
public void Execute(object parameter)
{
MessageBox.Show("Button Pressed!");
}
public event EventHandler CanExecuteChanged;
}
What I believe, maybe this is what I'm getting wrong is since Button & Textbox are in the View I don't need add/modify any method in my SpecialCommand implementation. They should be able to see when a property is changed.
Like the CanExecuteChanged() below, this command raises a lot of times and seems overkill for this small task.
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
There is no need to make it more complicated than it needs to be.
public class MainWindowVM
{
private string m_OneTwoThree;
public string OneTwoThree{
get { return OneTwoThree; }
set {
if (m_OneTwoThree != value){
m_OneTwoThree = value;
NotifyPropertyChanged(nameof(OneTwoThree)); //if you need this let me know
}
}
}
public MainWindowVM()
{
}
public ICommand RandomCommand { get { return new RelayCommand(OnRandom, IsOneTwoThree); } }
private void OnRandom()
{
//do stuff
}
private bool IsOneTwoThree(){
if (OneTwoThree == "123"){
return true;
}
return false;
}
}
You'll also have to update your xaml I don't think it'll be able to find 'OneTwoThree' so you'll have to bind it yourself, however you usually would.
<StackPanel>
<TextBox x:Name="textBox" Margin="0, 5" Text="{Binding OneTwoThree}"/>
<Button Content="Click me!" Margin="0, 5" Command="{Binding RandomCommand, Source={StaticResource WindowVm}}"/>
</StackPanel>
If you have any questions just ask.
This is my RelayCommand : Use RelayCommand("thing to execute", "if function returns true you can execute");
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null) {
return true;
}
else {
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
You need a way to tell the command that it should check whether it CanExecute. This is done by triggering the event CanExecuteChanged. It tells the button to requery the CanExecute property.
For this to work, I would add a text property to your view model and bind the textbox to it.
In SpecialCommand add the method:
public void TriggerCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
In the class MainWindowVM add the property:
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
_specialCommand.TriggerCanExecuteChanged();
}
}
}
View Model to implementing INotifyPropertyChanged (see comments):
public class MainWindowVM : INotifyPropertyChanged
{
public SpecialCommand SpecialCommand { get; set; } = new SpecialCommand();
private string _text;
public string Text
{
get { return _text; }
set {
if (value != _text) {
_text = value;
OnPropertyChanged(nameof(Text));
SpecialCommand.TriggerCanExecuteChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

WPF Binding to behavior property is not working

I have a custom behavior that take a boolean as parameter. I'm trying to bind it to a property in my model but I can't get it to work. (I'm using DevExpress)
<dxg:GridControl
x:Name="dgArticles"
Grid.Row="1"
AutoGenerateColumns="None"
ItemsSource="{Binding Path=Articles, Mode=TwoWay}">
<dxmvvm:Interaction.Behaviors>
<util:ExpandAllBehavior HasLotPartie="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CahierDesCharges.HasLotPartie }" />
</dxmvvm:Interaction.Behaviors>
</dxg:GridControl
This is a WPF UserControl and the DataContext is defined in code-behind as follow:
public partial class GridView : UserControl
{
public GridView(ArticleViewModel a)
{
InitializeComponent();
this.DataContext = a;
}
}
My ViewModel:
public class ArticleViewModel : INotifyPropertyChanged
{
private Cahier cahierDesCharges;
public Cahier CahierDesCharges { get { return cahierDesCharges; } set { } }
private ObservableCollection<Article> articles;
public ObservableCollection<Article> Articles
{
get { return articles; }
set { articles = value; OnPropertyChanged("articles"); }
}
public ArticleViewModel() { }
public ArticleViewModel(Cahier c)
{
this.cahierDesCharges = c;
this.articles = CahierDesCharges.Articles;
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
And the CahierDesCharges Class:
public class Cahier : INotifyPropertyChanged
{
public bool HasLotPartie { get; set; }
private ObservableCollection<Article> articles;
public ObservableCollection<Article> Articles
{
get { return articles; }
set { articles = value; OnPropertyChanged("articles"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public Cahier() { }
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
Console.WriteLine(propertyName);
}
}
The Behavior:
public class ExpandAllBehavior : Behavior<GridControl>
{
public static readonly DependencyProperty HasLotPartieProperty =
DependencyProperty.Register("HasLotPartie", typeof(Boolean), typeof(ExpandAllBehavior));
public bool HasLotPartie
{
get { return (bool)GetValue(HasLotPartieProperty); }
set { SetValue(HasLotPartieProperty, value); }
}
protected override void OnAttached()
{
if (HasLotPartie)
{
base.OnAttached();
this.AssociatedObject.CustomRowFilter += AssociatedObject_Loaded;
}
}
void AssociatedObject_Loaded(object sender, EventArgs e)
{
int dataRowCount = AssociatedObject.VisibleRowCount;
for (int rowHandle = 0; rowHandle < dataRowCount; rowHandle++)
AssociatedObject.ExpandMasterRow(rowHandle);
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
I have used the line "{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CahierDesCharges.HasLotPartie }" at a different place and I know it's working. In the following example it's working perfectly fine:
<dxg:GridControl.View>
<dxg:TableView
AllowScrollAnimation="True"
EnableImmediatePosting="True"
IsDetailButtonVisibleBinding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.CahierDesCharges.HasLotPartie }"
Name="view"
ShowGroupedColumns="False"
ShowGroupPanel="False"
UseLightweightTemplates="None" />
</dxg:GridControl.View>
The behavior works fine if I manually set the value to true: <util:ExpandAllBehavior HasLotPartie="True" /> So I thought that it might be that the DadaContext is not inherited and I use the trick explain by Thomas Levesque on his blog here and this is what I got:
<dxg:GridControl.Resources>
<util:BindingProxy x:Key="proxy" Data="{Binding}" />
</dxg:GridControl.Resources>
<dxmvvm:Interaction.Behaviors>
<util:ExpandAllBehavior HasLotPartie="{Binding Data.CahierDesCharges.HasLotPartie, Source={StaticResource proxy}}" />
</dxmvvm:Interaction.Behaviors>
I've used this trick several time and I know it works too but not in this scenario. Right now I'm stuck with this after several hours of searching so any help is very welcome.
Thank you
Ok so I figured that my CahierDesCharges was null when the behavior was called. Therefore HasLotPartie was false no matter what and so it did not reach my event. So I just moved my if() in AssociatedObject_Loaded method and it worked. I needed to wait until the GridControl was fully loaded so the CahierDesCharges wasn't null anymore.
This is my behavior class now:
public class ExpandAllBehavior : Behavior<GridControl>
{
public static readonly DependencyProperty HasLotPartieProperty =
DependencyProperty.Register("HasLotPartie", typeof(bool), typeof(ExpandAllBehavior));
public bool HasLotPartie
{
get { return (bool)GetValue(HasLotPartieProperty); }
set { SetValue(HasLotPartieProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded; //Apllied after GridControl is fully loaded.
this.AssociatedObject.CustomRowFilter += AssociatedObject_Loaded; //Applied after filtering on Rows
}
void AssociatedObject_Loaded(object sender, EventArgs e)
{
if (HasLotPartie)
{
int dataRowCount = AssociatedObject.VisibleRowCount;
for (int rowHandle = 0; rowHandle < dataRowCount; rowHandle++)
AssociatedObject.ExpandMasterRow(rowHandle);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
Now my MasterDetails are always expanded when HasLotPartie is true which was the wanted effect.
As always I was stuck focussing on complicated stuff while the solution was easy.

PropertyChanged is null wpf

Help me please!!!
I have 3 UserControls
I select user on List Users UC listbox
Then send message from SendMessage UC to Database
when i send message to Db it must refresh my chat listBox in Correspondence UC, but problem is in my ChatWrapper.
PropertyChanged in ChatWrapper is always null, and I can't refresh my ListBox in Correspondence UC with new message
List Users:
public IEnumerable<EmployeesDb> getListNames
{
get { return Db.Instance.EmployeesDbs.ToList(); }
}
static EmployeesDb m_selectedUser;
public static EmployeesDb selectedUser
{
get { return m_selectedUser; }
set
{
if (value != null)
m_selectedUser = value;
Correspondence correspondence = new Correspondence();
correspondence.CorrespondenceChat();
}
}
}
Send Message ( I try to refresh -> SendInfo.FirstOrDefault().RefreshGUI();)
public static DependencyProperty SendInfoProperty =
DependencyProperty.Register(
"SendInfo",
typeof(IEnumerable<ChatWrapper>),
typeof(SendMessage));
public IEnumerable<ChatWrapper> SendInfo
{
get { return GetValue(SendInfoProperty) as IEnumerable<ChatWrapper>; }
set { SetValue(SendInfoProperty, value); }
}
void SendMessageCommandExecute()
{
//...
SendInfo.FirstOrDefault().RefreshGUI();
//...
}
ChatWrapper
public class ChatWrapper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
public void RefreshGUI()
{
FirePropertyChanged("message");
}
public ChatDb chatDb { get; set; }
public string message
{
get
{
return (chatDb != null) ? string.Format("{0} {1}.{2} / {3} / {4}\n{5}",
chatDb.FromEmployeesDb.surname,
chatDb.FromEmployeesDb.name[0],
chatDb.FromEmployeesDb.middleName[0],
chatDb.messageDateTime,
chatDb.computerName,
chatDb.message) : null;
}
}
Correspondence
//...
public partial class Correspondence : UserControl, INotifyPropertyChanged
{
public static DependencyProperty GetCorrespondenceInfoProperty =
DependencyProperty.Register(
"GetCorrespondenceInfo",
typeof(IEnumerable<ChatWrapper>),
typeof(Correspondence),
new PropertyMetadata(OnChanged));
public IEnumerable<ChatWrapper> GetCorrespondenceInfo
{
get { return GetValue(GetCorrespondenceInfoProperty) as IEnumerable<ChatWrapper>; }
set { SetValue(GetCorrespondenceInfoProperty, value); }
}
static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var me = d as Correspondence;
me.chat = me.GetCorrespondenceInfo;
}
ICollectionView m_CollectionView;
public static IEnumerable<ChatWrapper> m_chat;
public IEnumerable<ChatWrapper> chat
{
get { return m_chat; }
set
{
m_chat = value;
if (ListUsers.selectedUser != null)
CorrespondenceChat();
FirePropertyChanged("chat");
}
}
public void CorrespondenceChat()
{
if (m_chat == null)
return;
m_CollectionView = CollectionViewSource.GetDefaultView(m_chat);
//...
FirePropertyChanged("chat");
}
XAML of Correspondence (refresh
<Grid>
<ListBox x:Name="correspondenceListBox" ItemsSource="{Binding chat, RelativeSource={RelativeSource AncestorType={x:Type local:Correspondence}}}"
Height="auto" Grid.Row="0" Grid.Column="1" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding message}" TextWrapping="Wrap" Width="auto"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I tried to write
public event PropertyChangedEventHandler PropertyChanged = delegate { };
PropertyChanged is no longer null, but it's still not updated

save checked items in Multiselectlist when navigating from/to

I would like to save which items have been checked in a multiselectlist so that when the page is navigated from and then back to the checked items may be shown in the list. Currently when I navigate away after checking items and then go back to the Multiselectlist item page, the checked states are not saved. So far what I have is as follows
Multiselectlist.xaml
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ScrollViewer>
<!--<toolkit:MultiselectList x:Name="ColorList" ItemsSource="{Binding}" Height="88" HorizontalAlignment="Left" VerticalAlignment="Top" >-->
<toolkit:MultiselectList x:Name="ColorList" HorizontalAlignment="Left" VerticalAlignment="Top" Tap="ColorList_Tap">
<toolkit:MultiselectList.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,0,0" Grid.ColumnSpan="2">
<!--<Rectangle Fill="{Binding Brush}" Width="50" Height="50"/>-->
<CheckBox Background="{Binding Brush}"/>
<TextBlock Text="{Binding Name}" Margin="12,10,0,0"/>
</StackPanel>
</DataTemplate>
</toolkit:MultiselectList.ItemTemplate>
</toolkit:MultiselectList>
</ScrollViewer>
Multiselectlist.xaml.cs
public ColorListPage()
{
InitializeComponent();
solidColorBrushList = new List<ColorItem>()
{
//Color Color = (Color)ColorConverter.ConvertFromString("#FFF0F8FF");
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FFF0F8FF"), Name = "alice blue" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FFFAEBD7"), Name = "antique white" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FF00FFFF"), Name = "aqua" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FF7FFFD4"), Name = "aquamarine" },
new ColorItem { Brush = ColorHelper.ToSolidColorBrush("#FFF0FFFF"), Name = "azure" }, //dont translate!?
};
this.ColorList.ItemsSource = solidColorBrushList;
this.Loaded += new RoutedEventHandler(ColorListPage_Loaded);
}
void ColorListPage_Loaded(object sender, RoutedEventArgs e)
{
//show checkboxes when page is loaded
this.ColorList.IsSelectionEnabled = true;
}
private void ColorList_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
DependencyObject tappedElement = e.OriginalSource as UIElement;
MultiselectItem tappedItem = this.FindParentOfType<MultiselectItem>(tappedElement);
ColorItem selectedItem = null;
if (tappedItem != null)
{
// DataContext contains reference to data item
selectedItem = tappedItem.DataContext as ColorItem;
}
if (selectedItem != null)
{
MessageBox.Show(selectedItem.Name + " Tapped");
}
}
private T FindParentOfType<T>(DependencyObject element) where T : DependencyObject
{
T result = null;
DependencyObject currentElement = element;
while (currentElement != null)
{
result = currentElement as T;
if (result != null)
{
break;
}
currentElement = VisualTreeHelper.GetParent(currentElement);
}
return result;
}
So how would I save the checked state of the items in the Multiselectlist so that if a user navigates away and then back to the page, those checked items are shown?
You can use the multiselectlist's SelectedItems property to select items. If you use the SelectionChanged event on the list you can update a list in your viewmodel to save when navigating away from the page. Using the blend sdk and its Interactivity dll, you can bind the event to a command in your viewmodel as well.
<phone:PhoneApplicationPage
<!-- the usual xmlns attributes -->
xmlns:vm="clr-namespace:MyApplication.Presentation.ViewModels"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<phone:PhoneApplicationPage.DataContext>
<vm:MyPageViewModel />
</phone:PhoneApplicationPage.DataContext>
<toolkit:MultiselectList x:Name="selectionlist" ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding UpdateSelectedCommand}"
CommandParameter="{Binding ElementName=selectionlist, Path=SelectedItems}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction
Command="{Binding LoadCommand}"
CommandParameter="{Binding ElementName='selectionlist'}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<toolkit:MultiselectList.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</toolkit:MultiselectList.ItemTemplate>
</toolkit:MultiselectList>
</phone:PhoneApplicationPage>
Then, in your viewmodel you can create the appropriate commands and methods like this.
namespace MyApplication.Presentation.ViewModels {
public sealed class MyPageViewModel : DependencyObject {
private readonly ObservableCollection<ItemViewModel> items;
private readonly RoutedCommand load;
private readonly RoutedCommand saveCommand;
private readonly RoutedCommand updateSelectedCommand;
public MyPageViewModel() {
items = new ObservableCollection<ItemViewModel>();
load = new RoutedCommand<MultiselectList>(
m => {
IEnumerable<Item> store = loadItems();
IEnumerable<Item> selected = loadSelectedItems();
populateSelectionList(m, store, selected);
});
updateSelectedCommand = new RoutedCommand<IList>(setSelected);
// use the savecommand on a button or a BindableApplicationBarButton
// or execute the command when you're navigating away from the page
saveCommand = new RoutedCommand<object>(o => storeItems(items));
}
public ICommand LoadCommand {
get { return load; }
}
public ICommand UpdateSelectedCommand {
get { return updateSelectedCommand; }
}
public ICommand SaveCommand {
get { return saveCommand; }
}
private void populateSelectionList(MultiselectList list, IEnumerable<Item> storage, IEnumerable<Item> selected) {
foreach (Item item in selected) {
ItemViewModel viewModel = new ItemViewModel(item);
list.SelectedItems.Add(viewModel);
items.Add(viewModel);
}
foreach (string item in storage) {
bool found = false;
foreach (Item select in selected) {
if (select == item) {
found = true;
break;
}
}
if (!found) {
ItemViewModel viewModel = new ItemViewModel(item);
items.Add(viewModel);
}
}
}
private void setSelected(IList list) {
// triggered when user selects or deselects an item in GUI
foreach (ItemViewModel viewModel in items) {
viewModel.IsSelected = false;
}
foreach (object item in list) {
ItemViewModel item = (ItemViewModel)item;
foreach (ItemViewModel tvm in items) {
if (tvm == item) {
tvm.IsSelected = true;
break;
}
}
}
}
private static void storeItems(IEnumerable<ItemViewModel> enumerable) {
// get all selected items
List<ItemViewModel> selected = new List<ItemViewModel>();
foreach (ItemViewModel viewModel in enumerable) {
if (viewModel.IsSelected) {
selected.Add(viewModel);
}
}
// save selected items in storage
saveSelectedItems(selected);
// save enumerable (i.e. all items) in storage
saveItems(enumerable);
}
private static void saveItems(IEnumerable<ItemViewModel> items) {
// todo: save enumerable to storage
foreach (ItemViewModel item in items) {
//saveItem(item.Model);
}
}
private static IEnumerable<Item> loadItems() {
// todo: load from storage
}
private static void saveSelectedItems(IEnumerable<Item> items) {
// todo: save selected to storage
}
private static IEnumerable<Item> loadSelectedItems() {
// todo: load from storage
}
}
}
Notice the todo items. You need to store and load the items from some storage, for instance a file. Take a look at IsolatedStorage in case you want to store it in a file.
You'll find the RoutedCommand class if you search on google for instance, it's inserted below for simplicity.
The Item and ItemViewModel classes are very basic, they just expose the fields necessary for displaying your item.
public class ItemViewModel : INotifyPropertyChanged {
private Item model;
public ItemViewModel(Item item) {
model = item;
item.PropertyChanged += (o,e) => onPropertyChanged(e.Property);
}
public bool IsSelected { get; set; }
public string Name {
get { return model.Name; }
set { model.Name = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string property) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(property));
}
}
}
public sealed class Item : INotifyPropertyChanged {
private string name;
public string Name {
get { return name; }
set {
if (name != value) {
name = value;
onPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(string property) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(property));
}
}
}
You can put the RoutedCommand class in a separate namespace if you'd like, and include it in your xaml file and in your viewmodels using statements.
public class RoutedCommand<TArg> : ICommand {
private readonly Action<TArg> execute;
private readonly Func<TArg, bool> canexecute;
public RoutedCommand(Action<TArg> execute) : this(execute, o => true) { }
public RoutedCommand(Action<TArg> execute, Func<TArg, bool> canexecute) {
this.execute = execute;
this.canexecute = canexecute;
}
public bool CanExecute(object parameter) {
return canexecute((TArg)parameter);
}
public void Execute(object parameter) {
if (CanExecute(parameter)) {
execute((TArg)parameter);
}
}
public event EventHandler CanExecuteChanged;
}

Categories

Resources