PROBLEM: Use one single viewModel with two different views.
I have a Window with a control ContentControl which is binded to a property in the DataContext, called Object MainContent {get;set;}. Base on a navigationType enum property, I assign other ViewModels to it to show the correct UserControl.
I need to merge two views into one ViewModel, and because I'm assigning a ViewModel to the ContentControl mentioned before, the TemplateSelector is not able to identify which is the correct view as both shares the same viewModel
If I assign the view instead the ViewModel to the ContentControl, the correct view is shown, however, non of the commands works.
Any Help? Thanks in advance.
SOLUTION: based on #mm8 answer and https://stackoverflow.com/a/5310213/2315752:
ManagePatientViewModel.cs
public class ManagePatientViewModel : ViewModelBase
{
public ManagePatientViewModel (MainWindowViewModel inMainVM) : base(inMainVM) {}
}
ViewHelper.cs
public enum ViewState
{
SEARCH,
CREATE,
}
MainWindowViewModel.cs
public ViewState State {get;set;}
public ManagePatientViewModel VM {get;set;}
private void ChangeView(ViewState inState)
{
State = inState;
// This is need to force the update of Content.
var copy = VM;
MainContent = null;
MainContent = copy;
}
public void NavigateTo (NavigationType inNavigation)
{
switch (inNavigationType)
{
case NavigationType.CREATE_PATIENT:
ChangeView(ViewState.CREATE);
break;
case NavigationType.SEARCH_PATIENT:
ChangeView(ViewState.SEARCH);
break;
default:
throw new ArgumentOutOfRangeException(nameof(inNavigationType), inNavigationType, null);
}
}
MainWindow.xaml
<DataTemplate x:Key="CreateTemplate">
<views:CreateView />
</DataTemplate>
<DataTemplate x:Key="SearchTemplate">
<views:SearchView/>
</DataTemplate>
<TemplateSelector x:Key="ViewSelector"
SearchViewTemplate="{StaticResource SearchTemplate}"
CreateViewTemplate="{StaticResource CreateTemplate}"/>
<ContentControl
Grid.Row="1"
Content="{Binding MainContent}"
ContentTemplateSelector="{StaticResource ViewSelector}" />
TemplateSelector.cs
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate SearchViewTemplate {get;set;}
public DataTemplate CreateViewTemplate {get;set;}
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (!(item is SelectLesionViewModel vm))
{
return null;
}
switch (vm.ViewType)
{
case ViewState.CREATE:
return CreateViewTemplate;
case ViewState.SEARCH:
return SearchViewTemplate;
default:
return null;
}
}
}
How is the TemplateSelector supposed to know which template to use when there are two view types mapped to a single view model type? This makes no sense I am afraid.
You should use two different types. You could implement the logic in a common base class and then define two marker types that simply derive from this implementation and add no functionality:
public class ManagePatientViewModel { */put all your code in this one*/ }
//marker types:
public class SearchPatientViewModel { }
public class CreatePatientViewModel { }
Also, you don't really need a template selector if you remove the x:Key attributes from the templates:
<DataTemplate DataType="{x:Type viewModels:SearchPatientViewModel}">
<views:SearchPatientView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:CreatePatientViewModel}">
<views:CreatePatientView />
</DataTemplate>
...
<ContentControl
Grid.Row="1"
Content="{Binding MainContent}" />
Maybe the requirement is to switch out the views and retain the one viewmodel.
Datatemplating is just one way to instantiate a view.
You could instead set the datacontext of the contentcontrol to the instance of your viewmodel and switch out views as the content. Since views are rather a view responsibility such tasks could be done completely in the view without "breaking" mvvm.
Here's a very quick and dirty approach illustrating what I mean.
I build two usercontrols, UC1 and UC2. These correspond to your various patient views.
Here's the markup for one:
<StackPanel>
<TextBlock Text="User Control ONE"/>
<TextBlock Text="{Binding HelloString}"/>
</StackPanel>
I create a trivial viewmodel.
public class OneViewModel
{
public string HelloString { get; set; } = "Hello from OneViewModel";
}
My mainwindow markup:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Content="UC1" Click="UC1_Click"/>
<Button Content="UC2" Click="UC2_Click"/>
</StackPanel>
<ContentControl Name="parent"
Grid.Column="1"
>
<ContentControl.DataContext>
<local:OneViewModel/>
</ContentControl.DataContext>
</ContentControl>
</Grid>
The click events switch out the content:
private void UC1_Click(object sender, RoutedEventArgs e)
{
parent.Content = new UC1();
}
private void UC2_Click(object sender, RoutedEventArgs e)
{
parent.Content = new UC2();
}
The single instance of oneviewmodel is retained and the view shown switches out. The hellostring binds and shows ok in both.
In your app you will want a more sophisticated approach to setting that datacontext but this sample is intended purely as a proof of concept to show you another approach.
Here's the working sample:
https://1drv.ms/u/s!AmPvL3r385QhgpgMZ4KgfMWUnxkRzA
Related
I'd like to create an app, containing the main menu (ribbonmenu) and different usercontrols, each assigned to an own ViewModel.
I was told to not implement classic events in code-behind but to use commands. So far, everything fine, commands for needed methods are implemented.
In my previous approach I "loaded" the UserControl, by assigning the corresponding ViewModel to a ContentControl, that loaded the UserControl, that was assigned to the ViewModel in MainWindow.Resource.
My last approach, simplified with a button instead of a menu:
<Window.Resources>
<DataTemplate x:Name="settingsViewTemplate" DataType="{x:Type viewmodels:SettingsViewModel}">
<views:SettingsView DataContext="{Binding SettingsVM, Source={StaticResource Locator}}"/>
</DataTemplate>
<DataTemplate x:Name="projectsViewTemplate" DataType="{x:Type viewmodels:ProjectViewModel}">
<views:ProjectView DataContext="{Binding ProjectVM, Source={StaticResource Locator}}"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Load Settings" Height="20" Margin="20 20 20 0" Click="ShowSettings"/>
<ContentControl Margin="5" Height="100" Content="{Binding}"/>
</StackPanel>
simplified code-behind:
public SettingsViewModel settingsViewModel;
public MainWindow()
{
InitializeComponent();
settingsViewModel = new SettingsViewModel();
}
private void ShowSettings(object sender, RoutedEventArgs e)
{
DataContext = settingsViewModel;
}
How can I load a UserControl, using ViewModel commands?
Don't use code-behind to handle view models. A View model should handle view models. Generally the same view model that implements the commands.
First create a main view model for the MainWindow as data source. This view model will also handle the switching between the views. It's recommended to let all page view models implement a common base type e.g. IPage.
Also you don't need any locator for this scenario. The views inside the DataTemplate will automatically have their DataContext set to the data type that maps to the DataTemplate. SettingsView will automatically have SetingsViewModel as the DataContext. If this would be the wrong context, then your model design is wrong.
IPage.cs
interface IPage : INotifyPropertyChanged
{
string PageTitel { get; set; }
}
SettingsViewModel.cs
class SettingsViewModel : IPage
{
...
}
ProjectViewModel.cs
class ProjectViewModel : IPage
{
...
}
PageName.cs
public enum PageName
{
Undefined = 0, SettingsPage, ProjectPage
}
MainViewModel.cs
An implementation of RelayCommand can be found at
Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern - Relaying Command Logic
class MainViewModel : INotifyPropertyChanged
{
public ICommand SelectPageCommand => new RelayCommand(SelectPage);
public Dictionary<PageName, IPage> Pages { get; }
private IPage selectedPage;
public IPage SelectedPage
{
get => this.selectedPage;
set
{
this.selectedPage = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
this.Pages = new Dictionary<PageName, IPage>
{
{ PageName.SettingsPage, new SettingsViewModel() },
{ PageName.ProjectPage, new ProjectViewModel() }
};
this.SelectedPage = this.Pages.First().Value;
}
public void SelectPage(object param)
{
if (param is PageName pageName
&& this.Pages.TryGetValue(pageName, out IPage selectedPage))
{
this.SelectedPage = selectedPage;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Name="settingsViewTemplate" DataType="{x:Type viewmodels:SettingsViewModel}">
<views:SettingsView />
</DataTemplate>
<DataTemplate x:Name="projectsViewTemplate" DataType="{x:Type viewmodels:ProjectViewModel}">
<views:ProjectView />
</DataTemplate>
</Window.Resources>
<StackPanel>
<!-- Content navigation -->
<StackPanel Orientation="Horizontal">
<Button Content="Load Settings"
Command="{Binding SelectPageCommand}"
CommandParameter="{x:Static PageName.SettingsPage}" />
<Button Content="Load Projects"
Command="{Binding SelectPageCommand}"
CommandParameter="{x:Static PageName.ProjectPage}" />
</StackPanel>
<ContentControl Content="{Binding SelectedPage}" />
<StackPanel>
</Window>
The short version:
public class MyViewModel : ViewModel
public MyViewModel()
{
View = new MyUserControlView();
View.DataContext = this; // allow the view to bind to the viewModel.
}
....
public UIElement View {
get; private set;
}
}
And then in XAML:
<ContentControl Content={Binding View} />
There are variations on this theme but that's the basic premise. e.g., if you have a ViewModel that can be bound to multiple views, or ViewModels that have lifetimes longer than their view, you can use a FrameViewModel class like this:
public class FrameViewModel : INotifyProperyChanged; {
public FrameViewModel(IViewModel viewModel; )
{
ViewModel = viewModel;
View = viewModel.CreateView();
View.DataContext = ViewModel;
}
public IViewModel ViewModel { get; set;...}
public UIElement View { get; set; }
}
And then bind THAT into the host XAML with a ContentControl binding to Frame.View.
A more pure approach is to the use the DataTemplateSelector class to instantiate the User Control in a DataTemplate. This is probably the method that WPF designers had in mind for connecting View and ViewModel in WPF. But it ends up spreading the mapping of View and ViewModel across three separate files (the custom C# DataTemplateSelector implementation; widely-separated static resource declaration and ContentControl wrapper in the hosting Window/Page; and the DataTemplate resources themselves which end up in resource files eventually if you have anything but a trivial number of ViewModel/View bindings.
Purists would argue, I suppose, that there's something dirty about having a viewmodel create a view. But there's something far more dirty about code to make DataTemplateSelectors work spread across five files, and inevitable complications with databindings that ensue while trying to tunnel a binding through a DataTemplate.
I got a generic base-class for ViewModel of my user-control:
public class SuggestModule<TEntity> : ViewModelBase
where TEntity : class, ISuggestable, new()
{
public SuggestModule(ISomeService someService)
{
// Some logic
}
// Some private fields, public properties, commands, etc...
}
}
Whitch has many inheritable classes. That is two of them, for example:
public class CitizenshipSuggestViewModel : SuggestModule<Citizenship>
{
public CitizenshipSuggestViewModel(ISomeService someService)
: base(someService) { }
}
public class PlaceOfBirthSuggestViewModel : SuggestModule<PlaceOfBirth>
{
public PlaceOfBirthSuggestViewModel(ISomeService someService)
: base(someService) { }
}
That is view implementation:
<catel:UserControl
x:Class="WPF.PRC.PBF.Views.UserControls.SuggestUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:pbf="clr-namespace:WPF.PRC.PBF">
<Grid>
<TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"> />
<ListBox ItemsSource="{Binding ItemsCollection}" />
// Other elements, behaviors, other extensive logic...
</Grid>
</catel:UserControl>
Now, in MainWindow creating two ContentControls:
<catel:Window
x:Class="WPF.PRC.PBF.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://schemas.catelproject.com">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{Binding CitizenshipSuggestViewModel, Converter={catel:ViewModelToViewConverter}}" />
<ContentControl Grid.Row="1" Content="{Binding PlaceOfBirthSuggestViewModel, Converter={catel:ViewModelToViewConverter}}" />
</Grid>
</catel:Window>
Due to violation of the Naming Convention, manually resolving a ViewModel in App.xaml.cs:
var viewModelLocator = ServiceLocator.Default.ResolveType<IViewModelLocator>();
viewModelLocator.Register(typeof(SuggestUserControl), typeof(CitizenshipSuggestViewModel));
viewModelLocator.Register(typeof(SuggestUserControl), typeof(PlaceOfBirthSuggestViewModel));
var viewLocator = ServiceLocator.Default.ResolveType<IViewLocator>();
viewLocator.Register(typeof(CitizenshipSuggestViewModel), typeof(SuggestUserControl));
viewLocator.Register(typeof(PlaceOfBirthSuggestViewModel), typeof(SuggestUserControl));
But now I have two views with identical ViewModels.
How can I solve this problem without creation of identical Views with repetition of the code in each of them?
Thank you in advance!
You have a few options:
Repeat yourself so if one needs customization in the future, you can customize just 1 without much overhead. The downside is that if you need to check the generic behavior, you'll need to change them all.
Create an enum representing the state that a VM is representing. In that case, you can simply creat a single vm that catches all of the cases that you need to handle. You can solve this using a dependency property on the view and by using ViewToViewModelMapping to automatically map this to the vm. This comes closest to achieving code-reusage as you wish to achieve with your view. It does go a bit against "separation of concerns", but since it represents the same sort of data I believe it's still a good approach.
For 2, you need to do the following:
1 Create an enum SuggestEntityType with PlaceOfBirth, Citizenship, etc
2 Create a property on the vm (this example code is assuming you are using Catel.Fody):
public SuggestedEntityType EntityType { get; set; }
3 Create a dependency property on the view:
[ViewToViewModel(MappingType = ViewToViewModelMappingType.ViewToViewModel)]
public SuggestedEntityType EntityType
{
get { return (SuggestedEntityType) GetValue(EntityTypeProperty); }
set { SetValue(EntityTypeProperty, value); }
}
public static readonly DependencyProperty EntityTypeProperty = DependencyProperty.Register("EntityType", typeof (SuggestedEntityType),
typeof (MyControl), new PropertyMetadata(null));
4 You can now use the user control like this:
<controls:MyView EntityType="Citizenship" />
For more info, see http://docs.catelproject.com/vnext/catel-mvvm/view-models/mapping-properties-from-view-to-view-model/
One possibility is to create a dependencyProperty in your UserControl codebehind, for example
#region Properties
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
#endregion Properties
#region Dependency Properties
public static readonly System.Windows.DependencyProperty TestProperty =
System.Windows.DependencyProperty.Register("Test", typeof(string), typeof(YourUserControl), new System.Windows.FrameworkPropertyMetadata() { BindsTwoWayByDefault = true });
#endregion Dependency Properties
Then in your xaml you can bind this property as :
<TextBlock Text="{Binding Test, RelativeSource={RelativeSource AncestorType={x:Type catel:UserControl}, Mode=FindAncestor}}">
Then in your MainWindow you can write:
<views:YourUserControlName Test="{Binding SomeTextPropertyFromMainWindowVM}"/>
So you will be able to bind from the property SomeTextPropertyFromMainWindowVM in your windowVM to some property in your userControl.
If you have in your main window several viewModels, you can write like:
<views:YourUserControlName Test="{Binding SomeViewModel.SomeTextProperty}"/>
<views:YourUserControlName Test="{Binding SomeOtherViewModel.SomeTextProperty}"/>
I created a user control that looks like a tile. Created another user control named TilePanel that serves as the default container of the tiles. And lastly, the very UI that looks like a Window start screen. I used RelayCommand to bind my TileCommands
Here are the codes:
Tilev2.xaml
<UserControl x:Class="MyNamespace.Tilev2"
Name="Tile"....
>
...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" Command="{Binding ElementName=Tile, Path=TileClickCommand}" >
</Button>
</UserControl>
Tilev2.xaml.cs
public partial class Tilev2 : UserControl
{
public Tilev2()
{
InitializeComponent();
}
//other DPs here
public ICommand TileClickCommand
{
get { return (ICommand)GetValue(TileClickCommandProperty); }
set { SetValue(TileClickCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for TileClickCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TileClickCommandProperty =
DependencyProperty.Register("TileClickCommand", typeof(ICommand), typeof(Tilev2));
}
}
Then I created a TilePanel user control as the container of the tiles
TilePanel.xaml
<UserControl x:Class="MyNamespace.TilePanel"
...
>
<Grid>
<ScrollViewer>
<ItemsControl Name="tileGroup"
ItemsSource="{Binding TileModels}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local2:Tilev2 TileText="{Binding Text}"
TileIcon="{Binding Icon}"
TileSize="{Binding Size}"
TileFontSize="{Binding FontSize}"
Background="{Binding Background}"
TileCaption="{Binding TileCaption}"
TileCaptionFontSize="{Binding TileCaptionFontSize}"
TileClickCommand="{Binding TileCommand}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
TilePanel.xaml.cs
public partial class TilePanel : UserControl
{
public TilePanel()
{
InitializeComponent();
DataContext = new TilePanelViewModel();
}
public TilePanelViewModel ViewModel
{
get { return (TilePanelViewModel)this.DataContext; }
}
}
My ViewModel for TilePanel
TilePanelViewModel.cs
public class TilePanelViewModel : ViewModelBase
{
private ObservableCollection _tileModels;
public ObservableCollection<TileModel> TileModels
{
get
{
if (_tileModels == null)
_tileModels = new ObservableCollection<TileModel>();
return _tileModels;
}
}
}
Then my Tile model
TileModel.cs
public class TileModel : BaseNotifyPropertyChanged
{
//other members here
ICommand tileCommand { get; set; }
//other properties here
public ICommand TileCommand
{
get { return tileCommand; }
set { tileCommand = value; NotifyPropertyChanged("TileCommand"); }
}
}
}
This is my StartScreen View where TilePanels with tiles should be displayed...
StartScreen.xaml
<UserControl x:Class="MyNamespace.StartMenu"
... >
<Grid>
<DockPanel x:Name="dockPanel1" Grid.Column="0" Grid.Row="1" Margin="50,5,2,5">
<local:TilePanel x:Name="tilePanel"></local:TilePanel>
</DockPanel>
</Grid>
</UserControl>
StartScreen.xaml.cs
public partial class WincollectStartMenu : UserControl, IView<StartMenuViewModel>
{
public WincollectStartMenu()
{
InitializeComponent();
}
public StartMenuViewModel ViewModel { get { return (DataContext as StartMenuViewModel); } }
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ViewModel.Tile = tilePanel.ViewModel.TileModels;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
return;
}
}
In my start screen ViewModel, I used ObservableCollection Tile
and use Tile.Add(tile); to populate my start screen with Tiles inside the TilePanel...
StartMenuViewModel.cs
TileModel tile = new TileModel() { Text = "Testing1", FontSize = 11, Size = TileSize.Medium, Background = (SolidColorBrush)new BrushConverter().ConvertFromString("#039BE5"), Tag="Something" };
tile.TileCommand = new RelayCommand(
p => Tile_TileClick(tile.Tag),
p => true
);
temp.Add(tile);
Now the problem is, if I add a new code below, tile = new TileModel() {...}
tile.TileCommand = new RelayCommand(...), even if I clicked on the first tile, my Tile_TileClick() will get the second tile's info (or the last tile inserted)...
Am I doing something wrong? Or Im doing everything wrong...?
This is not direct answer to your question, but hopefully it will give you few thoughts.
Ok, first of all, don't name your usercontrol like this:
<UserControl x:Class="MyNamespace.Tilev2" Name="Tile"/>
because the name can be easily overriden when using the usercontrol somewhere:
<local:Titlev2 Name="SomeOtherName" />
and the binding inside Tilevs with ElementName won't work: Command="{Binding ElementName=Tile, Path=TileClickCommand}"
Second, what's the point of Tilev2 usercontrol? Why don't just put the button directly to the DataTemplate inside TilePanel class?
If you need to reuse the template, you can put the template to resource dictionary.
If you need some special presentation code in the Tilev2 codebehind or you need to use the Tilev2 without viewmodel, it's better to create custom control instead of usercontrol in this case. it has much better design time support, and writing control templates it's easier (Triggers, DataTriggers, TempalteBinding, etc). If you used custom Control insead UserControl, you wouldn't have to write {Binding ElementName=Tile, Path=TileClickCommand}, or use RelativeSource, etc.
Third, it seems like you forced MVVM pattern where you can't really take advantage of it. Point of MVVM is separate application logic from presentation. But your Tile and TilePanel usercontrols are just presentation. You application logic could be in StartScreen which is concrete usage of TileName.
I would create custom controls called TilePanel (potentionally inherited from ItemsControl, Selector or ListBox) and if needed also for Tile. Both controls should not be aware of any viewmodels. There's absolutelly no need for that.
Take ListBox as an example. ListBox does not have viewmodel but can be easily used in MVVM scenarios. Just because ListBox it is not tied to any viewmodel, it can be databound to anything.
Just like ListBox creates ListBoxItems, or
Combobox creates ComboBoxItems, or
DataGrid creates DataGridRows or
GridView (in WinRT) creates GridViewRow, your TilePanel could create Tiles.
Bindings to tile specific properties, like Icon or Command could be specified in TilePanel.ItemContainerStyle orusing simillar appriach like DisplayMemberPath, resp ValueMemberPath in ListBox.
final usage could the look like:
<TilePanel ItemsSource="{Bidning ApplicationTiles}" />
or
<TilePanel>
<Tile Icon=".." Command=".." Text=".." />
<Tile Icon=".." Command=".." Text=".." />
</TilePanel>
Last, the name `TilePanel' evoked that it is some kind of panel like StackPanel, WrapPanel, etc. In other words, it is FrameworkElement inherited from Panel.
TilesView would be more suitable name for the control than TilePanel. The -View postfix is not from MVVM, it just follows naming convention -GridView, ListView...
Saw the problem...
To pass a parameter from button, I used CommandParameter so I could use it in switch-case scenario to know which button was clicked. But still, param was still null...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
TileCommand = new MyCommand() { CanExecuteFunc = param => CanExecuteCommand(), ExecuteFunc = param => Tile_TileClick(param)}
After 2 whole damn days, I changed it:
From this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding Tag, ElementName=Tile}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
To this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
My first post does error because CommandParameter does not know where to get its DataContext so I replaced it to CommandParameter={Binding} so it will get whatever from the DataContext.
I have a WinRT project based on prism with several pages, usercontrols etc.
For some reason I need to bind several views to a single model object accessed by the the viewmodels (each one belonging to a view).
The single model object is injected by a Unity container like other objects, that need to be singleton-like as the eventaggregator for instance.
To keep things simple I made an example with only one bool variable bound to a checkbox in each view, that should be synchronized over the views.
My problem is: When I check the box in the mainpage, the checkbox in a second page is following the value when navigating to that page(UserInpuPage in the example) BUT NOT the checkbox in the UserControl place on the Mainpage.
After a debug session I saw the variables in the single model having the right values, but the GUI on the Usercontrol (MyUserControl in the example) is not updated.
A Mechanism like GetBindingExpression(...) and then UpdateTarget() like in WPF seems not to exist in the WinRT library.
For design reasons (using prism mvvm I don't want to break the concept of the autowire and dynamical instantiation of the vm's) a static context defined in the resources section of the page and/or usercontrol is not what I'm looking for.
How can I achieve the update of the checkbox in the usercontrol with the model the same way as it works for the userinputpage after navigating?
Any help would be appreciated.
// Interface mendatory to work with Unitiy-Injection for RegisterInstance<ISingletonVM>(...)
public interface ISingletonVM
{
bool TestChecked{ get; set; }
}
public class SingletonVM : BindableBase, ISingletonVM
{
bool _testChecked = false;
public bool TestChecked
{
get
{
return _testChecked;
}
set
{
SetProperty(ref _testChecked, value);
}
}
}
This is the relevant code in the viewmodels (same for every vm, but vm from usercontrol in this case):
class MyUserControlViewModel : ViewModel
{
private readonly ISingletonVM _singletonVM;
public MyUserControlViewModel(ISingletonVM singletonVM)
{
_singletonVM = singletonVM;
}
public bool TestChecked
{
get
{
return _singletonVM.TestChecked;
}
set
{
_singletonVM.TestChecked = value;
}
}
}
Relevant XAML code fragments for the three views:
MainPage:
<prism:VisualStateAwarePage x:Name="pageRoot" x:Class="HelloWorldWithContainer.Views.MainPage"...>
...
<StackPanel Grid.Row="2" Orientation="Horizontal">
<ctrl:MyUserControl ></ctrl:MyUserControl>
<CheckBox IsChecked="{Binding TestChecked, Mode=TwoWay}" Content="CheckBox" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</StackPanel>
...
UserInputPage:
<prism:VisualStateAwarePage x:Name="pageRoot"
x:Class="HelloWorldWithContainer.Views.UserInputPage"
...
<CheckBox IsChecked="{Binding TestChecked, Mode=TwoWay}" Content="CheckBox" HorizontalAlignment="Left" Margin="440,190,0,0" VerticalAlignment="Top"/>
...
UserControl:
<UserControl
x:Class="HelloWorldWithContainer.Views.MyUserControl" prism:ViewModelLocator.AutoWireViewModel="True"
<Grid>
<CheckBox Content="CheckBox" IsChecked="{Binding TestChecked, Mode=TwoWay}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="282"/>
</Grid>
Your user control never gets notified about changes in MyUserControlViewModel.TestChecked property and that's why the view is never updated. One thing you can do to fix this is to subscribe to your SingletonVM.PropertyChanged event in the constructor of MyUserControlViewModel. Your ISingletonVM needs to implement the INotifyPropertyChanged interface. So the constructor of MyUserControlViewModel will be something like this:
public MyUserControlViewModel(ISingletonVM singletonVM)
{
_singletonVM = singletonVM;
_singletonVM.PropertyChanged += (sender, args) => OnPropertyChanged("TestChecked");
}
I want to change UserControls on button clicks (I'm not going to complicate here, so I'll only mention important parts). So idea was to bind ViewModels of those UserControls to ContentControl, and than associate them Views using DataTemplates.
Here's the code:
<Window x:Class="Project.MainWindow">
<Window.Resources>
<DataTemplate DataType="{x:Type UserControl:ViewUserControlViewModel}" >
<UserControl:ViewUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type UserControl:EditUserControlViewModel}" >
<UserControl:EditUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl DataContext="{Binding UserControlViewModel}" />
<Button Content="View" Click="ChangeToView()"/>
<Button Content="Edit" Click="ChangeToEdit()"/>
</Grid>
</Window>
ViewModel:
public class MainWindowViewModel : DependencyObject
{
public DependencyObject UserControlViewModel
{
get { return (DependencyObject)GetValue(UserControlViewModelProperty); }
set { SetValue(UserControlViewModelProperty, value); }
}
public static readonly DependencyProperty UserControlViewModelProperty =
DependencyProperty.Register("UserControlViewModel", typeof(DependencyObject), typeof(MainWindowViewModel), new PropertyMetadata());
public MainWindowViewModel()
{
UserControlViewModel = new EditUserControlViewModel();
}
}
But theres a problem. When I start project, I only see buttons but not any UserControls. What did I do wrong?
If your Window.DataContext is properly set to MainWindowViewModel this should do the job
<ContentControl Content="{Binding UserControlViewModel}" />
When doing mvvm your viewmodel should implement INotifyPropertyChanged and not inherit from DependencyObject.
public class MainWindowViewModel : INotifyPropertyChanged
{
private object _currentWorkspace; //instead of object type you can use a base class or interface
public object CurrentWorkspace
{
get { return this._currentWorkspace; }
set { this._currentWorkspace = value; OnPropertyChanged("CurrentWorkspace"); }
}
public MainWindowViewModel()
{
CurrentWorkspace= new EditUserControlViewModel();
}
//todo: to switch the workspace, create DelegeCommand/RelayCommand and set the CurrentWorkspace
//if you don't know about these commands let me know and i post it
public ICommand SwitchToViewCommand {get{...}}
public ICommand SwitchToEditCommand {get{...}}
}
xaml: you should set the Content Property to your CurrentWorkspace.
<ContentPresenter Content="{Binding UserControlViewModel}" />
<Button Content="View" Comamnd="{Binding SwitchToViewCommand}"/>
<Button Content="Edit" Comamnd="{Binding SwitchToEditCommand}"/>
! Don't forget to set the DataContext for your window to your MainWindowViewModel instance.
First of all you should post the code of your UserControl since (in your code snippet above) it's responsible for displaying some data.
Second you are not binding anything in your code.
Third your implementation of the ViewModel is wrong. You don't need to subclass a DependencyObject but instead implement the INotifyPropertyChanged interface in order to establish a ViewModel that is capable of notifying your View.
Fourth I don't know what you are doing with
<ContentControl DataContext="{Binding UserControlViewModel}" />
maybe you can explain further ?
Fifth when implementing the MVVM patterm (what you currently not do) you should avoid using events like the click event and instead use Commands.
(I know that's not a real answer yet, but I don't wanted to write in comment syntax)