I have a simple progress bar and status message textblock bound to a view model that should iterate in a foreach statement. it works fine in testing when a put a MessageBox in the foreach but if I take out the MessageBox the only status that's being captured is the last iteration and I can't determine what I need to do to correct that.
ViewModel -
public class ExampleViewModel : INotifyPropertyChanged
{
public RelayCommand<IList> Submit { get; }
private KeyValuePair<Model, string[]> _selectedItem;
public KeyValuePair<Model, string[]> SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
private void ExecuteSubmit(IList selectedItems)
{
string[] examples = new string[] {"a", "b", "c"};
double percentAge = 100/examples.Count;
if (examples.Count > 0)
{
foreach (var ex in examples)
{
CurrentProgress = CurrentProgress + percentAge;
MessageBox.Show("Step " + ex); // Status and Progress Bar update when this isn't commented out
// Thread.Sleep(1000);
StatusMessages = "Option " + ex;
}
}
return;
}
#region Status Fields
private string _statusMessages;
public string StatusMessages
{
get => _statusMessages;
set
{
if (_statusMessages != value)
{
_statusMessages = value;
RaisePropertyChanged(nameof(StatusMessages));
}
}
}
private double _currentProgress;
public double CurrentProgress
{
get => _currentProgress;
set
{
if (_currentProgress != value)
{
_currentProgress = value;
RaisePropertyChanged(nameof(CurrentProgress));
}
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(propertyName));
}
}
}
XAML -
<ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}"
Margin="5,5" Height="10" Width="240" HorizontalAlignment="Left"/>
<TextBlock Margin="5,5" Height="15" Width="240" TextWrapping="Wrap"
Foreground="Red"
Text="{Binding StatusMessages}"></TextBlock>
Calling Thread.Sleep(1000); on UI thread is wrong.. It freezes the UI and once the freeze ends it will start a new freeze so it could not even update any of the UI elements that are bound with the properties.. You can use DispatcherTimer instead
Replace
foreach (var ex in examples)
{
CurrentProgress = CurrentProgress + percentAge;
MessageBox.Show("Step " + ex); // Status and Progress Bar update when this isn't commented out
// Thread.Sleep(1000);
StatusMessages = "Option " + ex;
}
With
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
int ticksCount = 0;
var ticksLimit = examples.Count;
timer.Tick += (_, _) =>
{
ticksCount++;
if (ticksCount > ticksLimit)
timer.Stop();
else
{
var ex = examples[ticksCount - 1];
CurrentProgress = CurrentProgress + percentAge;
StatusMessages = "Option " + ex;
}
};
timer.Start();
I am using this code following code taken from Stackoverflow.
I want to transfer a string from one view model to another one on SelectionChanged event. But when I click on Tab2, I get Tab2 message box, but when I click on Tab1, I get both the message boxes indicating that both are getting executed. The same when I click Tab1, both message boxes are seen.
MainView.xaml
<TabControl>
<TabItem Header="My tab 1" Selector.IsSelected="{Binding IsMyTab1Selected}"> ... </TabItem>
<TabItem Header="My tab 2" Selector.IsSelected="{Binding IsMyTab2Selected}"> ... </TabItem>
</TabControl>
MainViewModel.cs
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainViewModel() {
PropertyChanged += handlePropertyChanged;
}
public bool IsMyTab1Selected {
get { return _IsMyTab1Selected ; }
set {
if (value != _IsMyTab1Selected ) {
_IsMyTab1Selected = value;
OnPropertyChanged("IsMyTab1Selected ");
}
}
}
private bool _IsMyTab1Selected = false;
public bool IsMyTab2Selected {
get { return _IsMyTab2Selected ; }
set {
if (value != _IsMyTab2Selected ) {
_IsMyTab2Selected = value;
OnPropertyChanged("IsMyTab2Selected ");
}
}
}
private bool _IsMyTab2Selected = false;
private void handlePropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "IsMyTab1Selected") {
MessageBox.Show("Tab_1 Clicked!");
} else if (e.PropertyName == "IsMyTab2Selected") {
MessageBox.Show("Tab_2 Clicked!");
}
}
I am not able to get the mutually exclusiveness, point me where I am wrong.
Option 1
you can change the setters to only call OnPropertyChanged(..) when the value is true:
public bool IsMyTab1Selected
{
get { return _IsMyTab1Selected; }
set
{
if (value != _IsMyTab1Selected)
{
_IsMyTab1Selected = value;
if (_IsMyTab1Selected)
OnPropertyChanged("IsMyTab1Selected");
}
}
}
public bool IsMyTab2Selected
{
get { return _IsMyTab2Selected; }
set
{
if (value != _IsMyTab2Selected)
{
_IsMyTab2Selected = value;
if(_IsMyTab2Selected)
OnPropertyChanged("IsMyTab2Selected");
}
}
}
Option 2
Or you can check in your handlePropertyChange() if the value is true like this
private void handlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsMyTab1Selected")
{
if(IsMyTab1Selected)
MessageBox.Show("Tab_1 Clicked!");
}
else if (e.PropertyName == "IsMyTab2Selected")
{
if(IsMyTab2Selected)
MessageBox.Show("Tab_2 Clicked!");
}
}
The bindings will update on deselection too. You need to check e.NewValue in your handler, or why not simply check _IsMyTab1Selected etc?
I have read several posts on this topic but many are from a previous versions of VS or framework. What I am trying to do is selected multiple rows from a dataGrid and return those rows into a bound observable collection.
I have tried creating a property(of type) and adding it to an observable collection and it works with single records but the code never fires with multiple records.
Is there a clean way to do this in VS2013 using an MVVM patern?
Any thoughts would be appreciated.
<DataGrid x:Name="MainDataGrid" Height="390" Width="720"
VerticalAlignment="Center" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False"
ItemsSource="{Binding Path=DisplayInDataGrid}"
SelectedItem="{Binding Path=DataGridItemSelected}"
SelectionMode="Extended"
private ObservableCollection<ScannedItem> _dataGridItemsSelected;
public ObservableCollection<ScannedItem> DataGridItemsSelected
{
get { return _dataGridItemsSelected; }
set
{
_dataGridItemsSelected = value;
OnPropertyChanged("DataGridItemsSelected");
}
}
private ScannedItem _dataGridItemSelected;
public ScannedItem DataGridItemSelected
{
get { return _dataGridItemSelected;}
set
{
_dataGridItemSelected = value;
OnPropertyChanged("DataGridItemSelected");
EnableButtons();
LoadSelectedCollection(DataGridItemSelected);
}
}
void LoadSelectedCollection(ScannedItem si)
{
if (DataGridItemsSelected == null)
{
DataGridItemsSelected = new ObservableCollection<ScannedItem>();
}
DataGridItemsSelected.Add(si);
}
I have implemented a two-way data binding for MultiSelector.SelectedItems property using attached behavior pattern.
The following image shows how it works:
There are two DataGrids bound to the same model and they share selected items. Left DataGrid is active so selected items are blue and right DataGrid is inactive so selected items are gray.
Following is the sample code how to use it:
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<DataGrid Grid.Column="1" ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2">
<Button DockPanel.Dock="Top" Content="Select All" Command="{Binding SelectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Unselect All" Command="{Binding UnselectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Select Next Range" Command="{Binding SelectNextRangeCommand}"/>
</StackPanel>
</Grid>
</Window>
Model.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
namespace WpfApplication
{
abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void Set<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
sealed class DelegateCommand : ICommand
{
private readonly Action action;
public DelegateCommand(Action action)
{
if (action == null)
throw new ArgumentNullException("action");
this.action = action;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute()
{
this.action();
}
bool ICommand.CanExecute(object parameter)
{
return true;
}
void ICommand.Execute(object parameter)
{
this.Execute();
}
}
class Person : ObservableObject
{
private string name, surname;
public Person()
{
}
public Person(string name, string surname)
{
this.name = name;
this.surname = surname;
}
public string Name
{
get { return this.name; }
set { this.Set(ref this.name, value, "Name"); }
}
public string Surname
{
get { return this.surname; }
set { this.Set(ref this.surname, value, "Surname"); }
}
public override string ToString()
{
return this.name + ' ' + this.surname;
}
}
class MainWindowModel : ObservableObject
{
public ObservableCollection<Person> People { get; private set; }
public SelectedItemCollection<Person> SelectedPeople { get; private set; }
public DelegateCommand SelectAllCommand { get; private set; }
public DelegateCommand UnselectAllCommand { get; private set; }
public DelegateCommand SelectNextRangeCommand { get; private set; }
public MainWindowModel()
{
this.People = new ObservableCollection<Person>(Enumerable.Range(1, 1000).Select(i => new Person("Name " + i, "Surname " + i)));
this.SelectedPeople = new SelectedItemCollection<Person>();
for (int i = 0; i < this.People.Count; i += 2)
this.SelectedPeople.Add(this.People[i]);
this.SelectAllCommand = new DelegateCommand(() => this.SelectedPeople.Reset(this.People));
this.UnselectAllCommand = new DelegateCommand(() => this.SelectedPeople.Clear());
this.SelectNextRangeCommand = new DelegateCommand(() =>
{
var index = this.SelectedPeople.Count > 0 ? this.People.IndexOf(this.SelectedPeople[this.SelectedPeople.Count - 1]) + 1 : 0;
int count = 10;
this.SelectedPeople.Reset(Enumerable.Range(index, count).Where(i => i < this.People.Count).Select(i => this.People[i]));
});
this.SelectedPeople.CollectionChanged += this.OnSelectedPeopleCollectionChanged;
}
private void OnSelectedPeopleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("Action = {0}, NewItems.Count = {1}, NewStartingIndex = {2}, OldItems.Count = {3}, OldStartingIndex = {4}, Total.Count = {5}", e.Action, e.NewItems != null ? e.NewItems.Count : 0, e.NewStartingIndex, e.OldItems != null ? e.OldItems.Count : 0, e.OldStartingIndex, this.SelectedPeople.Count);
}
}
class SelectedItemCollection<T> : ObservableCollection<T>
{
public void Reset(IEnumerable<T> items)
{
int oldCount = this.Count;
this.Items.Clear();
foreach (var item in items)
this.Items.Add(item);
if (!(oldCount == 0 && this.Count == 0))
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
if (this.Count != oldCount)
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
}
}
}
}
Following is the implementation, which unfortunately is not documented. Implementation uses various tricks (via reflection) to reduce the number of changes to underlying collections as much as possible (to suspend excessive collection changed notifications).
It is worth noting that if selected items collection in model has Select(IEnumerable) or Select(IEnumerable) method, that method will be used for performing bulk updates (updates which affect more than one item) which offers better performance if, for example, all items in the DataGrid are selected or un-selected.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApplication
{
static class MultiSelectorExtension
{
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(MultiSelectorExtension), new PropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));
private static readonly DependencyProperty SelectedItemsBinderProperty = DependencyProperty.RegisterAttached("SelectedItemsBinder", typeof(SelectedItemsBinder), typeof(MultiSelectorExtension));
[AttachedPropertyBrowsableForType(typeof(MultiSelector))]
[DependsOn("ItemsSource")]
public static IList GetSelectedItems(this MultiSelector multiSelector)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
return (IList)multiSelector.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(this MultiSelector multiSelector, IList selectedItems)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
multiSelector.SetValue(SelectedItemsProperty, selectedItems);
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var multiSelector = d as MultiSelector;
if (multiSelector == null)
return;
var binder = (SelectedItemsBinder)multiSelector.GetValue(SelectedItemsBinderProperty);
var selectedItems = e.NewValue as IList;
if (selectedItems != null)
{
if (binder == null)
binder = new SelectedItemsBinder(multiSelector);
binder.SelectedItems = selectedItems;
}
else if (binder != null)
binder.Dispose();
}
private sealed class SelectedItemsBinder : IDisposable
{
private static readonly IList emptyList = new object[0];
private static readonly Action<MultiSelector> multiSelectorBeginUpdateSelectedItems, multiSelectorEndUpdateSelectedItems;
private readonly MultiSelector multiSelector;
private IList selectedItems;
private IResetter selectedItemsResetter;
private bool suspendMultiSelectorUpdate, suspendSelectedItemsUpdate;
static SelectedItemsBinder()
{
GetMultiSelectorBeginEndUpdateSelectedItems(out multiSelectorBeginUpdateSelectedItems, out multiSelectorEndUpdateSelectedItems);
}
public SelectedItemsBinder(MultiSelector multiSelector)
{
this.multiSelector = multiSelector;
this.multiSelector.SelectionChanged += this.OnMultiSelectorSelectionChanged;
this.multiSelector.Unloaded += this.OnMultiSelectorUnloaded;
this.multiSelector.SetValue(SelectedItemsBinderProperty, this);
}
public IList SelectedItems
{
get { return this.selectedItems; }
set
{
this.SetSelectedItemsChangedHandler(false);
this.selectedItems = value;
this.selectedItemsResetter = GetResetter(this.selectedItems.GetType());
this.SetSelectedItemsChangedHandler(true);
if (this.multiSelector.IsLoaded)
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
else
{
RoutedEventHandler multiSelectorLoadedHandler = null;
this.multiSelector.Loaded += multiSelectorLoadedHandler = new RoutedEventHandler((sender, e) =>
{
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
this.multiSelector.Loaded -= multiSelectorLoadedHandler;
});
}
}
}
private int ItemsSourceCount
{
get
{
var collection = this.multiSelector.ItemsSource as ICollection;
return collection != null ? collection.Count : -1;
}
}
public void Dispose()
{
this.multiSelector.ClearValue(SelectedItemsBinderProperty);
this.multiSelector.Unloaded -= this.OnMultiSelectorUnloaded;
this.multiSelector.SelectionChanged -= this.OnMultiSelectorSelectionChanged;
this.SetSelectedItemsChangedHandler(false);
}
private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.suspendMultiSelectorUpdate || e.Action == NotifyCollectionChangedAction.Move)
return;
this.suspendSelectedItemsUpdate = true;
if (this.selectedItems.Count == 0)
this.multiSelector.UnselectAll();
else if (this.selectedItems.Count == this.ItemsSourceCount)
this.multiSelector.SelectAll();
else if (e.Action != NotifyCollectionChangedAction.Reset && (e.NewItems == null || e.NewItems.Count <= 1) && (e.OldItems == null || e.OldItems.Count <= 1))
UpdateList(this.multiSelector.SelectedItems, e.NewItems ?? emptyList, e.OldItems ?? emptyList);
else
{
if (multiSelectorBeginUpdateSelectedItems != null)
{
multiSelectorBeginUpdateSelectedItems(this.multiSelector);
this.multiSelector.SelectedItems.Clear();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
multiSelectorEndUpdateSelectedItems(this.multiSelector);
}
else
{
this.multiSelector.UnselectAll();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
}
}
this.suspendSelectedItemsUpdate = false;
}
private void OnMultiSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.suspendSelectedItemsUpdate)
return;
this.suspendMultiSelectorUpdate = true;
if (e.AddedItems.Count <= 1 && e.RemovedItems.Count <= 1)
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
else
{
if (this.selectedItemsResetter != null)
this.selectedItemsResetter.Reset(this.selectedItems, this.multiSelector.SelectedItems.Cast<object>().Where(item => item != CollectionView.NewItemPlaceholder));
else
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
}
this.suspendMultiSelectorUpdate = false;
}
private void OnMultiSelectorUnloaded(object sender, RoutedEventArgs e)
{
this.Dispose();
}
private void SetSelectedItemsChangedHandler(bool add)
{
var notifyCollectionChanged = this.selectedItems as INotifyCollectionChanged;
if (notifyCollectionChanged != null)
{
if (add)
notifyCollectionChanged.CollectionChanged += this.OnSelectedItemsCollectionChanged;
else
notifyCollectionChanged.CollectionChanged -= this.OnSelectedItemsCollectionChanged;
}
}
private static void UpdateList(IList list, IList newItems, IList oldItems)
{
int addedCount = 0;
for (int i = 0; i < oldItems.Count; ++i)
{
var index = list.IndexOf(oldItems[i]);
if (index >= 0)
{
object newItem;
if (i < newItems.Count && (newItem = newItems[i]) != CollectionView.NewItemPlaceholder)
{
list[index] = newItem;
++addedCount;
}
else
list.RemoveAt(index);
}
}
for (int i = addedCount; i < newItems.Count; ++i)
{
var newItem = newItems[i];
if (newItem != CollectionView.NewItemPlaceholder)
list.Add(newItem);
}
}
private static void GetMultiSelectorBeginEndUpdateSelectedItems(out Action<MultiSelector> beginUpdateSelectedItems, out Action<MultiSelector> endUpdateSelectedItems)
{
try
{
beginUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("BeginUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
endUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("EndUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
}
catch
{
beginUpdateSelectedItems = endUpdateSelectedItems = null;
}
}
private static IResetter GetResetter(Type listType)
{
try
{
MethodInfo genericReset = null, nonGenericReset = null;
Type genericResetItemType = null;
foreach (var method in listType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (method.Name != "Reset")
continue;
if (method.ReturnType != typeof(void))
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
continue;
var parameterType = parameters[0].ParameterType;
if (parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
genericResetItemType = parameterType.GetGenericArguments()[0];
genericReset = method;
break;
}
else if (parameterType == typeof(IEnumerable))
nonGenericReset = method;
}
if (genericReset != null)
return (IResetter)Activator.CreateInstance(typeof(GenericResetter<,>).MakeGenericType(genericReset.DeclaringType, genericResetItemType), genericReset);
else if (nonGenericReset != null)
return (IResetter)Activator.CreateInstance(typeof(NonGenericResetter<>).MakeGenericType(nonGenericReset.DeclaringType), nonGenericReset);
else
return null;
}
catch
{
return null;
}
}
private interface IResetter
{
void Reset(IList list, IEnumerable items);
}
private sealed class NonGenericResetter<TTarget> : IResetter
{
private readonly Action<TTarget, IEnumerable> reset;
public NonGenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items);
}
}
private sealed class GenericResetter<TTarget, T> : IResetter
{
private readonly Action<TTarget, IEnumerable<T>> reset;
public GenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable<T>>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable<T>>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items.Cast<T>());
}
}
}
}
}
Create a command that fires on the DataGrid's SelectionChanged event, passing in the DataGrid's SelectedItems.
In your ViewModel, have a List of selected objects.
Your SelectionChangedCommand execution method would then update that collection of selected objects.
For example:
In my XAML:
<DataGrid ItemsSource="{Binding Datasets, NotifyOnTargetUpdated=True}" Name="dsDatagrid" SelectionMode="Extended" MouseDoubleClick="ViewDataset">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=dsDatagrid, Path=SelectedItems}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
In my ViewModel:
private List<ObservableDataset> selectedDatasets;
private void SelectionChangedExecuted(object datasets)
{
this.selectedDatasets = new List<ObservableDataset>((datasets as IList).Cast<ObservableDataset>());
}
EDIT: I'm using MVVMLight.
there is probably a really simple reason why this isnt working but I've tried everything. I have a TextBlock with Text bound to a variable, the variable changes but the Text doesn't :
<TextBlock x:Name="modeLabel" Style="{StaticResource IndiTextBlock}" Height="23" TextWrapping="Wrap" Grid.Row="0" Text="{Binding ModeLabelText}" Margin="35,22,58,0"/>
The code that controls the text value is in a viewmodel:
public string ModeLabelText { get { return _modeLabeltext; } }
public ComboBoxItem SelectedMode { get { return _selectedMode; }
set
{
if (_selectedMode == value) return;
_selectedMode = value;
ToggleMode(null);
EvaluateScenario(null);
}
and
private void ToggleMode(object parameter)
{
if (_isBasicCalculation)
{
_modeLabeltext = "Target profit";
_isBasicCalculation = false;
}
else
{
_modeLabeltext = "Total to invest";
_isBasicCalculation = true;
}
}
Your class has to implement the INotifyPropertyChanged interface, and on changes of your variables, you should trigger the event
public class Model : INotifyPropertyChanged
{
public event EventHandler PropertyChanged; // event from INotifyPropertyChanged
protected void RaisePropertyChanged(string propertyName)
{
var local = PropertyChanged;
if (local != null)
{
local.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public void ToggleMode()
{
// ... your code ...
RaisePropertyChanged("ModelLabelText");
}
}
Thank you Nguyen Kien
private void ToggleMode(object parameter)
{
if (_isBasicCalculation)
{
_modeLabeltext = "Target profit";
OnPropertyChanged("ModeLabelText");
_isBasicCalculation = false;
}
else
{
_modeLabeltext = "Total to invest";
OnPropertyChanged("ModeLabelText");
_isBasicCalculation = true;
}
}
I'm using ItemsControl for StackPanel as below code:
File playBackControl.xaml - Begin
<ScrollViewer x:Name="scrollViewerChannelBtns">
<StackPanel x:Name="channelBtns" Orientation="Vertical" MouseWheel="ScrollViewer_MouseWheel">
<ItemsControl x:Name="channelBtnItems" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToogleButton x:Name="tgbChannelName"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{Binding Path=ChannelNameBtnWidth}"
Height="{Binding Path=ChannelNameBtnHeight}"
Margin="{Binding Path=ChannelNameBtnMargin}"
IsChecked="{Binding Path=IsChecked, Mode=TwoWay}"
ToolTip="{Binding Path=ToolTip}" Tag="{Binding Path=Index}"
IsEnabled="{Binding Path=IsEnabled}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
File playBackControl.xaml - End
File playBackControl.xaml.cs - Begin
public partial class PlayBackControl : UserControl
{
public static List<ChannelBtnItemData> listChannelBtnItemData = new List<ChannelBtnItemData>();
public PlayBackControl() //This will run first when the app start
{
InitializeComponent();
channelBtnItems.ItemContainerGenerator.StatusChanged += ChannelBtnItemsStatusChangedEventHandler;
System.Threading.Thread threadTimer = new System.Threading.Thread(TimerThreadThreadProc);
threadTimer.Start();
}
private void TimerThreadThreadProc()
{
while (true)
{
Thread.Sleep(60000); //Sleep 60s
this.Dispatcher.BeginInvoke(new Action(delegate()
{
//Re init listChannelBtnItemData, this list has about 64 items
channelBtnItems.ItemsSource = listChannelBtnItemData;
channelBtnItems.Items.Refresh();
}));
}
}
public class ChannelBtnItemData : INotifyPropertyChanged
{
private String _toolTip;
private int _index;
private int _channelID;
private bool _isChecked;
private bool _isEnabled;
private bool _lockToggle;
private double _channelNameBtnWidth;
private double _channelNameBtnHeight;
private Thickness _channelNameBtnMargin;
public String ToolTip
{
get { return _toolTip; }
set
{
_toolTip = value;
OnPropertyChanged("ToolTip");
}
}
public int Index
{
get { return _index; }
set
{
_index = value;
OnPropertyChanged("Index");
}
}
public int ChannelID
{
get { return _channelID; }
set
{
_channelID = value;
OnPropertyChanged("ChannelID");
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged("IsChecked");
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
public bool LockToggle
{
get { return _lockToggle; }
set
{
_lockToggle = value;
OnPropertyChanged("LockToggle");
}
}
public double ChannelNameBtnWidth
{
get { return _channelNameBtnWidth; }
set
{
_channelNameBtnWidth = value;
OnPropertyChanged("ChannelNameBtnWidth");
}
}
public double ChannelNameBtnHeight
{
get { return _channelNameBtnHeight; }
set
{
_channelNameBtnHeight = value;
OnPropertyChanged("ChannelNameBtnHeight");
}
}
public Thickness ChannelNameBtnMargin
{
get { return _channelNameBtnMargin; }
set
{
_channelNameBtnMargin = value;
OnPropertyChanged("ChannelNameBtnMargin");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public ChannelBtnItemData()
{
}
}
private void ChannelBtnItemsStatusChangedEventHandler(Object sender, EventArgs e)
{
if (channelBtnItems.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
if (channelBtnItems.HasItems)
{
var containers = channelBtnItems.Items.Cast<Object>().Select(
item => (FrameworkElement)channelBtnItems.ItemContainerGenerator.ContainerFromItem(item));
foreach (var container in containers)
{
if (container != null)
container.Loaded += ChannelBtnItemContainerLoaded;
}
}
}
}
private void ChannelBtnItemContainerLoaded(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
element.Loaded -= ChannelBtnItemContainerLoaded;
ToogleButton tgbChannelName = FindChild<ToogleButton>(element, "tgbChannelName");
if (tgbChannelName != null) //Sometimes It equal null
{
//Do something
}
}
public T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
else
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
}
File playBackControl.xaml.cs - End
In the ChannelBtnItemContainerLoaded() function, sometimes the tgbChannelName equal null,
I have searched and read more about this, but I don't know how to fix it.
I create a demo to test this but it worked for me. Loaded event is being fired when all is there available to access and so I never ran into the state where instance was null.
However I have a feeling that you haven't revealed us what you exactly up to there.
Are you changing the ItemsSource at runtime? At what point do you swap the ItemsSource?
Where is the following code of yours being called?
channelBtnItems.ItemContainerGenerator.StatusChanged += ChannelBtnItemsStatusChangedEventHandler;
channelBtnItems.ItemsSource = listChannelBtnItemData;
channelBtnItems.Items.Refresh();
However here is a trick how you can "postpone" an action.
Use the Dispatcher.BeginInvoke with DispatcherPrority.Background.
http://weblogs.asp.net/pawanmishra/archive/2010/06/06/understanding-dispatcher-in-wpf.aspx
private void ChannelBtnItemContainerLoaded(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
element.Loaded -= ChannelBtnItemContainerLoaded;
element.Dispatcher.BeginInvoke((Action)(() =>
{
ToggleButton tgbChannelName = FindChild<ToggleButton>(element, "tgbChannelName");
if (tgbChannelName != null) //Sometimes It equal null
{
//Do something
}
else
{
}
}), DispatcherPriority.Background);
}
Try it out. If this doesnt help please provide us with the complete code including data and viewmodel so can test on same code just like yours.