View, ViewModel and DataContext - c#

In order to solve a navigation issue in my application I have used an Event Aggregator which has solved the problem but has created an other one.
To navigate between different UserControls I used the Rachel's code you can find here which was working fine until I made some changes.
On the side of my screen I have a Main Menu (HomeViewModel()), by clicking on the items I switch between UserControls and in each of these UserControls there is a another menu bar where I can switch between other UserControls.
But this second menu (CateringMenuViewModel()) doesn't work anymore. The UserControl is displayed but nothing is happening when I am clicking in the menu bar.
At the first sight I thought it's because there is no DataContext.
So I added it in the code behind like this:
public CateringMenuView()
{
InitializeComponent();
this.DataContext = new CateringMenuViewModel(ApplicationService.Instance.EventAggregator);
}
But it still doesn't work.
I don't understand, the property Name is well bounded because the names are displayed in the menu but the command ChangePageCommand is not.
HomeViewModel
public class HomeViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public HomeViewModel()
{
// Add available pages
PageViewModels.Add(new HomeOrderViewModel());
PageViewModels.Add(new CateringMenuViewModel(ApplicationService.Instance.EventAggregator));
PageViewModels.Add(new HomeAdminViewModel());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
}
CateringMenuViewModel
public class CateringMenuViewModel : ObservableObject, IPageViewModel
{
protected readonly IEventAggregator _eventAggregator;
public CateringMenuViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator = eventAggregator;
PageViewModels.Add(new NewRegularOrderViewModel(ApplicationService.Instance.EventAggregator));
PageViewModels.Add(new NewDeliveryComOrderViewModel());
PageViewModels2.Add(new FillOrderViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
this._eventAggregator.GetEvent<GoToFillOrder>().Subscribe(GoToFillOrder);
}
public string Name
{
get
{
return "Catering";
}
}
public string imageSource
{
get
{
return "catering.ico";
}
}
#region Fields
private List<IUserContentViewModel> _pageViewModels;
public List<IUserContentViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IUserContentViewModel>();
return _pageViewModels;
}
}
private IUserContentViewModel _currentUserControl;
public IUserContentViewModel CurrentUserControl
{
get { return _currentUserControl; }
set
{
if (value != _currentUserControl)
{
_currentUserControl = value;
OnPropertyChanged("CurrentUserControl");
}
}
}
#region Methods
private void ChangeViewModel(IUserContentViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentUserControl = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
var x = this.GetHashCode();
}
#endregion
private ICommand _changePageCommand;
#endregion
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IUserContentViewModel)p),
p => p is IUserContentViewModel);
}
return _changePageCommand;
}
}
private void GoToFillOrder(int i)
{
CurrentUserControl = PageViewModels2[0];
}
}
CateringMenuView
<UserControl.Resources>
<DataTemplate DataType="{x:Type cvm:NewDeliveryComOrderViewModel}">
<cv:NewDeliveryComOrderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:NewRegularOrderViewModel}">
<cv:NewRegularOrderView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:FillOrderViewModel}">
<cv:FillOrderView/>
</DataTemplate>
</UserControl.Resources>
<Grid Margin="5">
<Grid>
<StackPanel>
<Menu>
<MenuItem Header="New Order">
<ItemsControl ItemsSource="{Binding PageViewModels}" Width="168" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay}" CommandParameter="{Binding}" TextDecorations="{x:Null}">
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</MenuItem>
</Menu>
</StackPanel>
</Grid>
<ContentControl Content="{Binding CurrentUserControl}"/>
</Grid>

Two problems here.
First off, you do not want to set the .DataContext of your UserControl manually because you want to use the CateringMenuViewModel from PageViewModels[1], not create a new instance of it.
So definitely remove the line of code
DataContext = new CateringMenuViewModel(ApplicationService.Instance.EventAggregator);
Second problem is why your event is not firing. I took a look at your code in your question's version history, and I do not see you broadcasting the event anywhere.
This line of code is correct to say "any time an event of type GoToFillOrder is broadcast, run the method GoToFillOrder"
_eventAggregator.GetEvent<GoToFillOrder>().Subscribe(GoToFillOrder);
however I don't see any code which actually broadcasts that event. You need a line of code like the following to broadcast the GoToFillOrder message to throughout your application :
_eventAggregator.GetEvent<GoToFillOrder>().Publish();

I finally found the solution.
In CateringMenuView(), I have replaced
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay}"
CommandParameter="{Binding}"
TextDecorations="{x:Null}">
by
<Hyperlink Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"
TextDecorations="{x:Null}">
Big thanks to Rachel!

Related

WPF MVVM: How to avoid reloading Views once they are not shown anymore

I have an application with many views and their related viewmodel. My problem is that every time I switch the displayed view (I have two different ways to do that: with a TabControl and with a ContentControl and the effect is the same) the view reaload completely and this has two main issues with my app:
This cause every animation I have to fire again (for example I maintain in the viewmodel what items of a TreeView are expanded and this cause to reopen it with the animation);
If the view I switched into has many objects to load this causes very long delays to reload it.
This is an example of how I switch from views:
XAML:
<Button Name="ProdNavigation"
Command="{Binding CmdNavigateTab}"
CommandParameter="{Binding Production}"/>
<Button Name="MachNavigation"
Command="{Binding CmdNavigateTab}"
CommandParameter="{Binding Machine}"/>
<Button Name="ProgNavigation"
Command="{Binding CmdNavigateTab}"
CommandParameter="{Binding Programming}"/>
<ContentControl x:Name="NavigationView"
Margin="5"
BorderBrush="{StaticResource SelectedTab}"
BorderThickness="1"
Content="{Binding SelectedView}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vmprod:ProductionViewModel}">
<vwprod:ProductionView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vmmachine:MachineViewModel}">
<vwmachine:MachineView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vmprog:ProgrammingViewModel}">
<vwprog:ProgrammingView/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
ViewModel:
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyChange([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ProductionViewModel production;
public ProductionViewModel Production => production;
private MachineViewModel machine;
public MachineViewModel Machine => machine;
private ProgrammingViewModel programming;
public ProgrammingViewModel Programming => programming;
private PlugIn selectedView;
public PlugIn SelectedView {
get => selectedView;
set {
selectedView = value;
NotifyChange();
}
}
private void NavigateTab(object parameter) {
if (parameter is PlugIn _plugIn) {
if (SelectedView != null) {
SelectedView.Selected = false;
SelectedView.PlugInDisable();
}
_plugIn.Selected = true;
_plugIn.PlugInEnable();
SelectedView = _plugIn;
}
}
Thank you very much in advance and pardon me in case the question is not clear, I am new to this.

Navigate through TabItem using MVVm

Assum that I have 3 user Control(TIShowNames,TIEnterCode,TIShowFactor).
they have their views and their corresponding viewModel.
all these 3, are in mainwindowView.
Here is my mainwindowView Xaml:
<Controls:TransitionPresenter Name="transContainer" Grid.Row="2" RestDuration="0:0:1" IsLooped="False" Transition="{StaticResource SlideTransition}">
<TabControl Name="TCMain" Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabItem Name="TIShowNames" Visibility="Collapsed">
<views:NameView x:Name="NameViewElement" />
</TabItem>
<TabItem Name="TIEnterCode" Visibility="Collapsed">
<views:CodeView x:Name="CodeViewElement" />
</TabItem>
<TabItem Name="TIShowFactor" Visibility="Collapsed">
<views:FactorDetailView x:Name="FactorDetailViewElement" />
</TabItem>
</TabControl>
</Controls:TransitionPresenter>
In my old Programming style i used to use this line of code for navigating through tab items(without any pattern):
private void ChangeTabItemTo(TabItem TI)
{
transContainer.ApplyTransition("TCMain", "TCMain");
TCMain.SelectedItem = TI;
}
I have a btn show in "TIShowNames", so when i clicks on that it has to go to "TIShowFactor".
In MVVM, ViewModel does not know any thing about view(this item tab is in its parent view!!!). so how he can change selected Tab Item without violating MVVM??
Another Try:
Changing Selectedindex wont work because of this error:
"System.Windows.Data Error: 40 : BindingExpression path error: 'Index'
property not found on 'object' ''MainWindowViewModel'
(HashCode=22018304)'. BindingExpression:Path=AAA;
DataItem='MainWindowViewModel' (HashCode=22018304); target element is
'TabControl' (Name=''); target property is 'IsSelected' (type
'Boolean')"
Update:
Controls:TransitionPresenter is from Fluid DLL
Update:
I want to hide tab item's header so no one can click the header and navigatoin through header is possibe only via btns in usercontrols
You could define a DataTemplate per view model type in the view:
<TabControl Name="TCMain"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ViewModel}"
Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:NameViewViewModel}">
<views:NameView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CodeViewViewModel}">
<views:CodeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
<views:FactorDetailView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
...and bind the SelectedItem property to a source property that you set in your view model, e.g.:
public object ViewModel
{
get { return _vm; }
set { _vm = value; NotifyPropertyChanged(); }
}
...
ViewModel = new CodeViewViewModel(); //displays the CodeView
Expanding on mm8's answer, this is how I'd do it:
First of all, I would create a BaseViewModel class to be inherited by every view model that will represent each tab of the TabControl.
I like to implement it as an abstract class with an abstract string property called "Title", so I can dynamically create the tabs and display their names (or titles). This class would also implement the NotifyPropertyChanged interface.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public abstract string Title { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I would create each view model inheriting from this base view model. for example:
public class NameViewModel : BaseViewModel
{
public override string Title
{
get
{
return "Name";
}
}
}
You would do the same for the other view models, only changing the "title" property of each of them.
Now I would create the MainView of the application and its corresponding view model.
The MainViewModel would have a collection of BaseViewModels and a "CurrentViewModel" (of type BaseViewModel) and would add all the view models you want to its collection on its constructor, like this:
public class MainViewModel : BaseViewModel
{
public override string Title
{
get
{
return "Main";
}
}
private ObservableCollection<BaseViewModel> _viewModels;
public ObservableCollection<BaseViewModel> ViewModels
{
get { return _viewModels; }
set
{
if (value != _viewModels)
{
_viewModels = value;
OnPropertyChanged();
}
}
}
private BaseViewModel _currentViewModel;
public BaseViewModel CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (value != _currentViewModel)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
public MainViewModel()
{
ViewModels = new ObservableCollection<BaseViewModel>();
ViewModels.Add(new NameViewModel());
ViewModels.Add(new CodeViewModel());
ViewModels.Add(new FactorDetailViewModel());
}
}
Finally, your main view would be similar to what mm8 posted:
(Notice the differences from my code to mm8's code: (1) You need to set the DisplayMemberPath of the TabControl to the "Title" property of the BaseViewModels and (2) You need to set the DataContext of the Window to your MainViewModel)
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="TCMain"
ItemsSource="{Binding ViewModels}"
DisplayMemberPath="Title"
SelectedItem="{Binding CurrentViewModel}"
Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:NameViewModel}">
<local:NameView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:CodeViewModel}">
<local:CodeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
<local:FactorDetailView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Now it should work as expected. Everytime you change the active tab of the TabControl, the SelectedItem property of the control will change to the corresponding view model, which will be templated as its corresponding view.
This approach is called "View Model First" (instead of View First), by the way.
EDIT
If you want to have a button on one of the view models that has a command to change the current view model, this is how you do it:
I suppose you are familiarized with Josh Smith's RelayCommand. If you are not, just search for its implementation on the web.
You will need to create an ICommand property on your MainViewModel, which will be responsible to change the "CurrentViewModel" property:
private ICommand _showFactorDetailCommand;
public ICommand ShowFactorDetailCommand
{
get
{
if (_showFactorDetailCommand == null)
{
_showFactorDetailCommand = new RelayCommand(p => true, p => show());
}
return _showFactorDetailCommand;
}
}
private void show()
{
CurrentViewModel = ViewModels.Single(s => s.Title == "Factor");
}
The show() method above simply searches the collection of view models that has the title "Factor" and set it to the CurrentViewModel, which in turn will be the Content of the ContentControl that acts as the ContentTemplate of your TabControl inside your main view.
Remember that your FactorDetailViewModel should be implemented as follows:
public class FactorDetailViewModel : ViewModelBase
{
public override string Title
{
get
{
return "Factor";
}
}
}
The button inside your "NameView" will bind to this command which is a property of "MainViewModel" using RelativeSource binding:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowFactorDetailCommand}" Content="Show Factor" Height="20" Width="60"/>
You could make this command more generic, passing the title of the view model you would like to navigate to as the command parameter:
private ICommand _showCommand;
public ICommand ShowCommand
{
get
{
if (_showCommand == null)
{
_showCommand = new RelayCommand(p => true, p => show(p));
}
return _showCommand;
}
}
private void show(p)
{
var vm = (string)p;
CurrentViewModel = ViewModels.Single(s => s.Title == vm);
}
Then on your views, pass the Command Parameter too:
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowCommand}" Content="Show Factor" CommandParameter="Factor" Height="20" Width="60"/>
Finally, to hide your TabItems completely, you need to set the ItemContainerStyle of your TabControl so that the Visibility of your TabItems has the value of "Collapsed".
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</TabControl.ItemContainerStyle>

Navigate between UserControls with Event Aggegator

I have a MainWindow where I navigate between UserControls by clicking on a menu and it works fine.
I am using this following pattern:
https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/
In one of those usercontrol there is a button. By clicking on this button I want to navigate to another usercontrol.
How do I do that?
MainView
<UserControl.Resources>
<DataTemplate DataType="{x:Type cvm:FirstViewModel}">
<cv:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:SecondViewModel}">
<cv:SecondView/>
</DataTemplate>
<cvm:MainViewModel x:Key="main"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource main}}">
<Border Grid.Row="0">
<Menu Height="58">
<ItemsControl ItemsSource="{Binding PageViewModels}" Width="289" Height="58">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay, Source={StaticResource main}}" CommandParameter="{Binding}" TextDecorations="{x:Null}">
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Menu>
</Border>
<Border Grid.Row="1" >
<ContentControl Content="{Binding CurrentUserControl}"/>
</Border>
</Grid>
MainViewModel
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
PageViewModels.Add(new FirstViewModel());
PageViewModels.Add(new SecondViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
}
#region Fields
private List<IUserContentViewModel> _pageViewModels;
public List<IUserContentViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IUserContentViewModel>();
return _pageViewModels;
}
}
private IUserContentViewModel _currentUserControl;
public IUserContentViewModel CurrentUserControl
{
get { return _currentUserControl; }
set
{
if (value != _currentUserControl)
{
_currentUserControl = value;
OnPropertyChanged("CurrentUserControl");
}
}
}
#region Methods
private void ChangeViewModel(IUserContentViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentUserControl = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
private ICommand _changePageCommand;
#endregion
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IUserContentViewModel)p),
p => p is IUserContentViewModel);
}
return _changePageCommand;
}
}
}
SecondView
<Grid Background="Blue">
<Button /> <!-- Going to ThirdView?????????-->
</Grid>
You have to call the ChangePageCommand from your button:
<Button DataContext="{Binding Source={StaticResource main}}"
Command="{Binding ChangePageCommand"}
CommandParameter="{Binding PageViewModels[2]}">
I am assuming that you have your FirstViewModel stored at PageViewModels[0], and your SecondViewModel at PageViewModels[1].
You also have to create a ThirdView link to your ThirdViewModel, as your other two Views/ViewModels:
<UserControl.Resources>
...
<DataTemplate DataType="{x:Type cvm:ThirdViewModel}">
<cv:ThirdView/>
</DataTemplate>
</UserControl.Resources>
And just as an advice, you can set your UserControl DataContext at the start of your code, instead of using it in any UIElement(Button and Grid in your case), like this:
<UserControl.DataContext>
<cvm:MainViewModel />
</UserControl.DataContext>
EDIT>>>>
Forgot to say that you also have to add your ThirdViewModel to your PageViewModels collection:
PageViewModels.Add(new ThirdViewModel());
I finally have my solution.
I use Event Aggregator with Prism 6.
First I create a Singleton.
internal sealed class ApplicationService
{
private ApplicationService() { }
private static readonly ApplicationService _instance = new ApplicationService();
internal static ApplicationService Instance { get { return _instance; } }
private IEventAggregator _eventAggregator;
internal IEventAggregator EventAggregator
{
get
{
if (_eventAggregator == null)
_eventAggregator = new EventAggregator();
return _eventAggregator;
}
}
}
Then public class GoToThird : PubSubEvent<TEvent> { }
In MainViewModel I subscribe to the event and add my ThirdViewModel().
public class MainViewModel : ViewModelBase
{
protected readonly IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
PageViewModels.Add(new FirstViewModel());
PageViewModels.Add(new SecondViewModel(ApplicationService.Instance.EventAggregator)));
PageViewModels.Add(new ThirdViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
this._eventAggregator = eventAggregator;
}
private void GoToThird()
{
CurrentUserControl = PageViewModels[2];
}
}
At the end I publish the event in SecondViewModel()
public class SecondViewModel
{
protected readonly IEventAggregator _eventAggregator;
public SecondViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator = eventAggregator;
}
private void Go()
{
_eventAggregator.GetEvent<GoToThird>().Publish();
}
private ICommand goToThirdCommand;
public ICommand GoToThirdCommand
{
get
{
return goToThirdCommand ?? (goToThirdCommand = new RelayCommand(p => this.Go(), p => this.CanGo()));
}
}
private bool CanGo()
{
return true;
}
}
Big Thanks to Rachel and Kirenenko

WPF MVVM moving a UserControl from one ObservableCollection to another by event

I have a checklist view that has 2 ScrollViewers. One checklist is for incomplete items, the other is for complete items. They are populated by 2 separate observable collections and bound to by ItemsControls.
The UserControl has a button, when clicked will move that 'check' to the other collection.
Currently the way I have this setup is in the ViewModel that's the DataContext for the UserControl there is a public event that is subscribed to by the main window's VM by using:
((CheckItemVM) ((CheckListItem) cli).DataContext).CompleteChanged += OnCompleteChanged;
where cli is the checklist item.
then the OnCompleteChanged finds the appropriate View object by using:
foreach (object aCheck in Checks)
{
if (aCheck.GetType() != typeof (CheckListItem)) continue;
if (((CheckListItem) aCheck).DataContext == (CheckItemVM) sender)
{
cliToMove = (CheckListItem) aCheck;
break;
}
}
It's pretty obvious this breaks MVVM and I'm looking for a way around it (CheckListItem is the View, and CheckItemVM is it's DataContext ViewModel). Reasoning for the boxed type is I've got another UserControl that will have instances inside both, which are basically section labels, and I need to be able to sort my observable collections where there is an association between the checklistitem to a specific section by name.
This can be done in MVVM using commands, and bindings....
The idea that I propouse here is to create a command in the Windows view model, that manage the check command, and this command to receive the item view model in the params, then manage the the things in the command. I'm going to show you a simple example, using MvvmLight library:
The model:
public class ItemViewModel : ViewModelBase
{
#region Name
public const string NamePropertyName = "Name";
private string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name == value)
{
return;
}
RaisePropertyChanging(NamePropertyName);
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion
#region IsChecked
public const string IsCheckedPropertyName = "IsChecked";
private bool _myIsChecked = false;
public bool IsChecked
{
get
{
return _myIsChecked;
}
set
{
if (_myIsChecked == value)
{
return;
}
RaisePropertyChanging(IsCheckedPropertyName);
_myIsChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
#endregion
}
A simple model with two property, one for the name (an identifier) and another for the check status.
Now in the Main View Model, (or Windows view model like you want)....
First the Collections, one for the checked items, and another for the unchecked items:
#region UncheckedItems
private ObservableCollection<ItemViewModel> _UncheckedItems;
public ObservableCollection<ItemViewModel> UncheckedItems
{
get { return _UncheckedItems ?? (_UncheckedItems = GetAllUncheckedItems()); }
}
private ObservableCollection<ItemViewModel> GetAllUncheckedItems()
{
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(1,10))
{
toRet.Add(new ItemViewModel {Name = string.Format("Name-{0}", i), IsChecked = false});
}
return toRet;
}
#endregion
#region CheckedItems
private ObservableCollection<ItemViewModel> _CheckedItems;
public ObservableCollection<ItemViewModel> CheckedItems
{
get { return _CheckedItems ?? (_CheckedItems = GetAllCheckedItems()); }
}
private ObservableCollection<ItemViewModel> GetAllCheckedItems()
{
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(11, 20))
{
toRet.Add(new ItemViewModel { Name = string.Format("Name-{0}", i), IsChecked = true });
}
return toRet;
}
#endregion
And the command:
#region CheckItem
private RelayCommand<ItemViewModel> _CheckItemCommand;
public RelayCommand<ItemViewModel> CheckItemCommand
{
get { return _CheckItemCommand ?? (_CheckItemCommand = new RelayCommand<ItemViewModel>(ExecuteCheckItemCommand, CanExecuteCheckItemCommand)); }
}
private void ExecuteCheckItemCommand(ItemViewModel item)
{
//ComandCode
item.IsChecked = true;
UncheckedItems.Remove(item);
CheckedItems.Add(item);
}
private bool CanExecuteCheckItemCommand(ItemViewModel item)
{
return true;
}
#endregion
The magic here could be in the Data binding, in this case I used command parameter and the FindAncestor binding, check the Data Template:
<DataTemplate x:Key="UncheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{Binding IsChecked}" IsEnabled="False"/>
<Button Content="Check" Width="75" Command="{Binding DataContext.CheckItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" CommandParameter="{Binding Mode=OneWay}"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="CheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="{Binding IsChecked}" IsEnabled="False"/>
</StackPanel>
</Grid>
</DataTemplate>
One data template for checked items, and another for unchecked items. Now the usage, this is simpler:
<ListBox Grid.Row="2" Margin="5" ItemsSource="{Binding UncheckedItems}" ItemTemplate="{DynamicResource UncheckedItemDataTemplate}"/>
<ListBox Grid.Row="2" Margin="5" Grid.Column="1" ItemsSource="{Binding CheckedItems}" ItemTemplate="{DynamicResource CheckedItemDataTemplate}"/>
This is a cleaner solution, hope is helps.

Calling event in UserControl from MainWindow

I have a UserControl with a Button and a ListView.
Model
public class Item
{
private string _name = string.Empty;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
ViewModel
public class ViewModel : NotifyProperty
{
private Command addCommand;
public ICommand AddCommand
{
get
{
if (addCommand == null)
addCommand = new Command(addItem);
return addCommand;
}
}
private ObservableCollection<Item> _itemCollection;
public ViewModel()
{
ItemCollection = new ObservableCollection<Item>();
Item newItem = new Item();
newItem.Name = "Joe";
ItemCollection.Add(newItem);
}
public ObservableCollection<Item> ItemCollection
{
get
{
return _itemCollection;
}
set
{
_itemCollection = value;
OnPropertyChanged("ItemCollection");
}
}
private void addItem(Object obj)
{
Item newItem = new Item();
newItem.Name = "Chris";
ItemCollection.Add(newItem);
}
}
UserControl (XAML)
<UserControl.DataContext>
<local:ViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate x:Key="ItemTemplate">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<DockPanel>
<Button Width="100" Height="30" Content="Add" Command="{Binding AddCommand}" DockPanel.Dock="Top" />
<ListView ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding ItemCollection}" />
</DockPanel>
</Grid>
I then add this to my MainWindow like so
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.mainContentControl.Content = new ListControl();
}
}
This works fine and when I click the "Add" button the name "Chris" gets added to the ListView.
Now I add a button to MainView and bind its Command property to my ViewModel like so:
<Grid>
<DockPanel>
<Button Width="100" Height="30" Content="Add" Command="{Binding AddCommand}" DockPanel.Dock="Top">
<Button.DataContext>
<local:ViewModel />
</Button.DataContext>
</Button>
<ContentControl x:Name="mainContentControl" />
</DockPanel>
</Grid>
When I click this button in the MainWindow the command is sent to the ViewModel, the addItem event gets called, the name "Chris" gets added to the ItemCollection, but the ListView doesn't update. What am I doing wrong?
Is your ViewModel being set as the data context of another element somewhere else (either in XAML or code-behind).
Where you're setting it as the data context against the button, that will instantiate a new instance of the view model, so any interaction with the instance the button has access to will not update across other instances.
The button will inherit the data context from ancestor elements (e.g. the window etc), so you shouldn't need to set it, but if you do need a separate data context for the button, then I'd recommend creating the instance of ViewModel as a resource and then just referencing that for the elements that need access to it.

Categories