MVVM – Hierarchies & Navigation Implicit Binding for UWP - c#

Somewhat I've been trying to follow this MVVM tutorial using Hierarchies and navigation:
https://www.tutorialspoint.com/mvvm/mvvm_hierarchies_and_navigation.htm
I've done so far most of the tutorial, but when it comes to UWP it seems that Implicit binding is not available for UWP, so I can't replicate this tutorial, because even though I've used x:DataType with x:Key that the compiler asks for an x:key attribute in order to bind views with viewmodel, all I get is the fullname of my viewmodel instead of being able to see the actual content.
So can somebody help me how can I use hierarchies properly in UWP using Plain MVVM pattern without the help of tools such as MVVM Light or MVVM Cross.
I'll leave you the code I have so far for a UWP app:
<Page
x:Class="MVVMHeirarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHeirarchiesDemo"
xmlns:views="using:MVVMHeirarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHeirarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.
We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
<viewmodel:MainPageViewModel/>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="CustomerTemplate" x:DataType="viewmodel:CustomerListViewModel">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate x:Key="OrderTemplate" x:DataType="viewmodel:OrderViewModel">
<views:OrderView/>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="NavBar"
Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Content="Customers"
Command="{Binding NavCommand}"
CommandParameter="customers"
Grid.Column="0"
Grid.Row="0"/>
<Button Content="Orders"
Command="{Binding NavCommand}"
CommandParameter="orders"
Grid.Column="2"
Grid.Row="0"/>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Grid>
as you can see may main trouble are at my Page. Resources I guess with my binding at
because that line of code is not getting access to the actual content of my views.
this is my viewmodel for my main view:
namespace MVVMHeirarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class MainPageViewModel : BindableBase
{
public MainPageViewModel()
{
NavCommand = new MyCommand<string>(OnNavigation);
}
private CustomerListViewModel _customerListViewModel = new CustomerListViewModel();
private OrderViewModel _orderViewModel = new OrderViewModel();
private BindableBase _currentViewModel;
public BindableBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
SetProperty(ref _currentViewModel, value);
}
}
public MyCommand<string> NavCommand { get; private set; }
private void OnNavigation(string destination)
{
switch (destination)
{
case "orders":
{
CurrentViewModel = _orderViewModel;
break;
}
case "customers":
default:
CurrentViewModel = _customerListViewModel;
break;
}
}
}
}
and finally this is my helper bindable class:
namespace MVVMHeirarchiesDemo
{
/*The main idea behind this class is to encapsulate the INotifyPropertyChanged implementation
* and provide helper methods to the derived class so that they can easily trigger the appropriate notifications.
* Following is the implementation of BindableBase class.*/
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName]string propertyName = null)
{
if (object.Equals(member, val))
return;
member = val;
OnPropertyChanged(propertyName);
}
}
}
I hope someone can help me out with this one.

well It took me a while but i was able to understand how to use DataTemplateSelector class, it turns out that this work around fit nicely to my problem since there is no implicit binding for UWP.
So if you are exploring UWP there isnt IMPLICIT BINDING you have to use explicit binding in order to make this specific problem work.
So this was my solution, I still use my BindableBase because it help me to choose the proper ViewModel for the View i'm calling, so there is no need to change it.
firstable I've created a Template class so I can retrieve the properties that will help me work with ExplicitBinding:
public class Template
{
public string DataType { get; set; }
public DataTemplate DataTemplate { get; set; }
}
Then I'll use a Collection but I'll use a Facade Pattern on this one, I kind love patterns are fun and elegant.
public class TemplateCollection : Collection<Template>
{
}
Then I've used a class by another answer that helps me to reuse it to solve my problem:
public class MyDataTemplateSelector : DataTemplateSelector
{
public TemplateCollection Templates { get; set; }
private ICollection<Template> _templateCache { get; set; }
public MyDataTemplateSelector()
{
}
private void InitTemplateCollection()
{
_templateCache = Templates.ToList();
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (_templateCache == null)
{
InitTemplateCollection();
}
if (item != null)
{
var dataType = item.GetType().ToString();
var match = _templateCache.Where(m => m.DataType == dataType).FirstOrDefault();
if (match != null)
{
return match.DataTemplate;
}
}
return base.SelectTemplateCore(item, container);
}
}
this class can be found here: How to associate view with viewmodel or multiple DataTemplates for ViewModel?
just I dont like to steal credits, but I did learned from this answer and my partner sugesstion MLavoie.
so this is my Fix view thanks to this I'm able to use navigation and also created a Hierarchical MVVM Pattern.
<Page
x:Class="MVVMHeirarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHeirarchiesDemo"
xmlns:dtempsltor="using:MVVMHeirarchiesDemo.Templates"
xmlns:views="using:MVVMHeirarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHeirarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.
We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
<viewmodel:MainPageViewModel/>
</Page.DataContext>
<Page.Resources>
<dtempsltor:TemplateCollection2 x:Key="templates">
<dtempsltor:Template DataType="MVVMHeirarchiesDemo.ViewModel.CustomerListViewModel">
<dtempsltor:Template.DataTemplate>
<DataTemplate x:DataType="viewmodel:CustomerListViewModel">
<views:CustomerListView/>
</DataTemplate>
</dtempsltor:Template.DataTemplate>
</dtempsltor:Template>
<dtempsltor:Template DataType="MVVMHeirarchiesDemo.ViewModel.OrderViewModel">
<dtempsltor:Template.DataTemplate>
<DataTemplate x:DataType="viewmodel:OrderViewModel">
<views:OrderView/>
</DataTemplate>
</dtempsltor:Template.DataTemplate>
</dtempsltor:Template>
</dtempsltor:TemplateCollection2>
<dtempsltor:MyDataTemplateSelector x:Key="MyDataTemplateSelector"
Templates="{StaticResource templates}"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="NavBar"
Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Content="Customers"
Command="{Binding NavCommand}"
CommandParameter="customers"
Grid.Column="0"
Grid.Row="0"/>
<Button Content="Orders"
Command="{Binding NavCommand}"
CommandParameter="orders"
Grid.Column="2"
Grid.Row="0"/>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl ContentTemplateSelector="{StaticResource MyDataTemplateSelector}"
Content="{Binding CurrentViewModel}"/>
</Grid>
</Grid>
thanks for those who helped me enlight myself it was great, to be able to solve it with some hints and also felt good to be able to solve it, I kinda love to code in C# and Xaml.

Related

WPF Navigation while Keeping Menubar (Header) and Footer Fixed

We used to develop application with WinForms and nowadays we are trying to migrate it to WPF, starting from zero. In our application we have 3 main parts on screen which are Header (all main menu items), body (based on MDI container, content can be changed) and the footer (where general status is displayed, logo etc.) Whenever a user clicks on different menuitem from header part, the body part would change it's children to that Panel/Form.
There are lot's of good examples/tutorials on the Internet but I am confused about how to achieve to create a navigation service that allows to switch the view of body part.
Any suggestions would be appriciated, thanks in advance.
There are indeed multiple ways to archive this result. I will try and explain the very basic/easiest way to get the result.
While this will not provide example in combination with Menu Control, I think it will help you to understand the concept
In your MainWindow you can split use Grid layout and split the space into 3 parts as you wanted. Your Main window Xaml should look something like this :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<ContentControl x:Name="Header"/>
<ContentControl x:Name="Content" Grid.Row="1/>
<ContentControl x:Name="Footer" Grid.Row="2"/>
</Grid>
In your content control you can insert your "UserControls" for the Header,Content,Footer. Now to the navigation part:
As mentioned there are many ways to archive this and I will describe what I do consider the easiest way (not the most flexible way however, so keep that in mind).
First I will suggest to make a navigation Model:
public class NavigationModel
{
public NavigationModel(string title, string description, Brush color)
{
Title = title;
Description = description;
Color = color;
}
public string Title { get; set; }
public string Description { get; set; }
public Brush Color { get; set; }
public override bool Equals(object obj)
{
return obj is NavigationModel model &&
Title == model.Title &&
Description == model.Description &&
Color == model.Color;
}
public override int GetHashCode()
{
return HashCode.Combine(Title, Description, Color);
}
}
We create a new class that will handle the navigation collection, lets call it navigation service.
public class NavigationService
{
public List<NavigationModel> NavigationOptions { get=>NavigationNameToUserControl.Keys.ToList(); }
public UserControl NavigateToModel(NavigationModel _navigationModel)
{
if (_navigationModel is null)
//Or throw exception
return null;
if (NavigationNameToUserControl.ContainsKey(_navigationModel))
{
return NavigationNameToUserControl[_navigationModel].Invoke();
}
//Ideally you should throw here Custom Exception
return null;
}
//Usage of the Func, provides each call new initialization of the view
//If you need initialized views, just remove the Func
//-------------------------------------------------------------------
//Readonly is used only for performance reasons
//Of course there is option to add the elements to the collection, if dynamic navigation mutation is needed
private readonly Dictionary<NavigationModel, Func<UserControl>> NavigationNameToUserControl = new Dictionary<NavigationModel, Func<UserControl>>
{
{ new NavigationModel("Navigate To A","This will navigate to the A View",Brushes.Aqua), ()=>{ return new View.ViewA(); } },
{ new NavigationModel("Navigate To B","This will navigate to the B View",Brushes.GreenYellow), ()=>{ return new View.ViewB(); } }
};
#region SingletonThreadSafe
private static readonly object Instancelock = new object();
private static NavigationService instance = null;
public static NavigationService GetInstance
{
get
{
if (instance == null)
{
lock (Instancelock)
{
if (instance == null)
{
instance = new NavigationService();
}
}
}
return instance;
}
}
#endregion
}
This service will provide us with action to receive desired UserControll (note that I am using UserControl instead of pages, since they provide more flexibility).
Not we create additional Converter, which we will bind into the xaml:
public class NavigationConverter : MarkupExtension, IValueConverter
{
private static NavigationConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter is null)
{
_converter = new NavigationConverter();
}
return _converter;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
NavigationModel navigateTo = (NavigationModel)value;
NavigationService navigation = NavigationService.GetInstance;
if (navigateTo is null)
return null;
return navigation.NavigateToModel(navigateTo);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
In our MainWindows.xaml add reference to the Converter namespace over xmlns, for example :
xmlns:Converter="clr-namespace:SimpleNavigation.Converter"
and create insance of converter :
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
Note that your project name will have different namespace
And set the Add datacontext to the instance of our Navigation Service:
You can do it over MainWindow.Xaml.CS or create a ViewModel if you are using MVVM
MainWindow.Xaml.CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Service.NavigationService.GetInstance.NavigationOptions;
}
}
Now all is left to do is navigate. I do not know how about your UX, so I will just provide example from my github of the MainWindow.xaml. Hope you will manage to make the best of it :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView
x:Name="NavigationList"
ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<Border
Height="35"
BorderBrush="Gray"
Background="{Binding Color}"
ToolTip="{Binding Description}"
BorderThickness="2">
<TextBlock
VerticalAlignment="Center"
FontWeight="DemiBold"
Margin="10"
Text="{Binding Title}" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentControl
Grid.Column="1"
Content="{Binding ElementName=NavigationList,Path=SelectedItem,Converter={StaticResource NavigationConverter}}"/>
</Grid>
Just in case I will leave you a link to github, so it will be easier for you
https://github.com/6demon89/Tutorials/blob/master/SimpleNavigation/MainWindow.xaml
Using same principle to use Menu Navigation
<Window.DataContext>
<VM:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Converter:NavigationConverter x:Key="NavigationConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="Navigaiton"
ItemsSource="{Binding NavigationOptions}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem
Command="{Binding DataContext.NavigateCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}"
Header="{Binding Title}"
Background="{Binding Color}"
ToolTip="{Binding Description}">
</MenuItem>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
<ContentControl
Grid.Row="1"
Background="Red"
BorderBrush="Gray"
BorderThickness="2"
Content="{Binding CurrentView,Converter={StaticResource NavigationConverter}}"/>
<Border Grid.Row="2" Background="{Binding CurrentView.Color}">
<TextBlock Text="{Binding CurrentView.Description}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
And we have in VM List of the navigation Models, Current Model and the navigation command :
public class MainViewModel:INotifyPropertyChanged
{
public List<NavigationModel> NavigationOptions { get => NavigationService.GetInstance.NavigationOptions; }
private NavigationModel currentView;
public NavigationModel CurrentView
{
get { return currentView; }
set
{
currentView = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CurrentView"));
}
}
RelayCommand _saveCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ICommand NavigateCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(Navigate);
}
return _saveCommand;
}
}
private void Navigate(object param)
{
if(param is NavigationModel nav)
{
CurrentView = nav;
}
}
}
Sorry for long reply
I think that you not necessary have to start from scratch. You may have a look:
https://qube7.com/guides/navigation.html

calling a method from ViewModel when DataContext changes

The situation:
I have a little app that works with fantasy classes. In the example below I boiled it down to the bare bones. In a ComboBox, situated in the Main Window, the user selects a fantasy class (warrior, rogue, mage etc.) from a list loaded from a DB. This information is passed to a UserControl sitting in Main Window which exposes details about the class using MVVM and data binding. All of this works so far.
The DB has a value (in this case Gear) saved as an int which at the moment displays as an int on screen. It's the app's responsibility to parse that to a string.
So the question is: How do I wire up a method in the UserControl's ViewModel to trigger whenever it's associated View has a DataContext (the selected CharacterClass) change?
Main Window:
<Window x:Class="ExampleApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ExampleApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ComboBox Height="22" MinWidth="70"
ItemsSource="{Binding Classes}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedClass}"/>
<local:DetailsView Grid.Column="1" DataContext="{Binding SelectedClass}"/>
</Grid>
</Window>
Main Window ViewModel:
namespace ExampleApp
{
class MainWindowViewModel : Observable
{
private ObservableCollection<CharacterClass> _Classes;
private CharacterClass _SelectedClass;
public ObservableCollection<CharacterClass> Classes
{
get { return _Classes; }
set { SetProperty(ref _Classes, value); }
}
public CharacterClass SelectedClass
{
get { return _SelectedClass; }
set { SetProperty(ref _SelectedClass, value); }
}
public MainWindowViewModel()
{
LoadCharacterClasses();
}
private void LoadCharacterClasses()
{
//simulated data retrieval from a DB.
//hardcoded for demo purposes
Classes = new ObservableCollection<CharacterClass>
{
//behold: Gear is saved as an int.
new CharacterClass { Name = "Mage", Gear = 0, Stats = "3,2,1" },
new CharacterClass { Name = "Rogue", Gear = 1, Stats = "2,2,2" },
new CharacterClass { Name = "Warrior", Gear = 2, Stats = "1,2,3" }
};
}
}
}
My CharacterClass definition. Inheriting from Observable which encapsulates INotifyPropertyChanged
namespace ExampleApp
{
public class CharacterClass : Observable
{
private string _Name;
private int _Gear;
private string _Stats;
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public int Gear
{
get { return _Gear; }
set { SetProperty(ref _Gear, value); }
}
public string Stats
{
get { return _Stats; }
set { SetProperty(ref _Stats, value); }
}
}
}
Details about the Observable baseclass:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExampleApp
{
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The DetailsView UserControl:
<UserControl x:Class="ExampleApp.DetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ExampleApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:DetailsViewModel}">
<local:DetailsView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Label Content="Name:"/>
<Label Content="Base Stats"/>
<Label Content="Starting Gear"/>
</StackPanel>
<StackPanel Grid.Column="1">
<Label Content="{Binding Name}"/>
<Label Content="{Binding Stats}"/>
<Label Content="{Binding gearToString}"/>
</StackPanel>
</Grid>
</UserControl>
and finally: the DetailsViewModel:
public class DetailsViewModel : Observable
{
public string GearToString;
//The method I would like to have called whenever the selected
//CharacterClass (DetailsView.DataContext, so to speak) changes.
private void OnCharacterClassChanged(int gearNumber)
{
switch (gearNumber)
{
case 0:
GearToString = "Cloth";
break;
case 1:
GearToString = "Leather";
break;
case 2:
GearToString = "Plate";
break;
default:
GearToString = "*Error*";
break;
}
}
}
I've fiddled around with attempting to have a command fire when the DetailsView Label updates.
Made a failed attempt to convert DetailsViewModel.GearToString to a dependencyproperty.
I've attempted to override Observable's SetProperty inside of DetailsViewModel.
I don't know which, if any of, those attempts would be viable, if I managed to implement them properly (I've only been coding for several months now :))
I could get it to work using DetailsView code-behind, however that's not MVVM'y.
Because you change your DetailViews DataContext via the combobox, you can access the "current" DetailDataContext before the combobox changes SelectedItem.
You can do this right here:
public CharacterClass SelectedClass
{
get { return _SelectedClass; }
set {
_SelectedClass.DoWhatever();
SetProperty(ref _SelectedClass, value);
}
}
Or you can handle the ComboBoxes SelectionChanged event via a command. Your old value is in e.RemovedItem.
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0)
(e.RemovedItems[0] as CharacterClass).DoSomething();
}
I tend to prefer that approach since it can get confusing quickly if you put too much logic in the setters. It leads to chain reactions that are pretty hard to follow and debug.
In general viewmodels communicate with each other via events. In more complex / disconnected situations with the help of an EventAggregator, MessageBus or something similiar.

How to get ComboBox content value?

I would like to get content from my combobox. I already tried some ways to do that, but It doesn't work correctly.
This is example of my combobox:
<ComboBox x:Name="cmbSomething" Grid.Column="1" Grid.Row="5" HorizontalAlignment="Center" Margin="0 100 0 0" PlaceholderText="NothingToShow">
<ComboBoxItem>First item</ComboBoxItem>
<ComboBoxItem>Second item</ComboBoxItem>
</ComboBox>
After I click the button, I want to display combobox selected item value.
string selectedcmb= cmbSomething.Items[cmbSomething.SelectedIndex].ToString();
await new Windows.UI.Popups.MessageDialog(selectedcmb, "Result").ShowAsync();
Why this code does not work?
My result instead of showing combobox content, it shows this text:
Windows.UI.Xaml.Controls.ComboBoxItem
You need the Content property of ComboBoxItem. So this should be what you want:
var comboBoxItem = cmbSomething.Items[cmbSomething.SelectedIndex] as ComboBoxItem;
if (comboBoxItem != null)
{
string selectedcmb = comboBoxItem.Content.ToString();
}
I have expanded on my suggestion regarding using models instead of direct UI code-behind access. These are the required parts:
BaseViewModel.cs
I use this in a lot of the view models in my work project. You could technically implement it directly in a view model, but I like it being centralized for re-use.
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Hashtable values = new Hashtable();
protected void SetValue(string name, object value)
{
this.values[name] = value;
OnPropertyChanged(name);
}
protected object GetValue(string name)
{
return this.values[name];
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
ComboViewModel.cs
This what you'll bind to make it easy to get values. I called it ComboViewModel because I'm only dealing with your ComboBox. You'll want a much bigger view model with a better name to handle all of your data binding.
public class ComboViewModel : BaseViewModel
{
public ComboViewModel()
{
Index = -1;
Value = string.Empty;
Items = null;
}
public int Index
{
get { return (int)GetValue("Index"); }
set { SetValue("Index", value); }
}
public string Value
{
get { return (string)GetValue("Value"); }
set { SetValue("Value", value); }
}
public List<string> Items
{
get { return (List<string>)GetValue("Items"); }
set { SetValue("Items",value); }
}
}
Window1.xaml
This is just something I made up to demonstrate/test it. Notice the various bindings.
<Window x:Class="SO37147147.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbSomething" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" HorizontalAlignment="Center" MinWidth="80"
ItemsSource="{Binding Path=Items}" SelectedIndex="{Binding Path=Index}" SelectedValue="{Binding Path=Value}"></ComboBox>
<TextBox x:Name="selectedItem" MinWidth="80" Grid.Row="2" Grid.Column="0" Text="{Binding Path=Value}" />
<Button x:Name="displaySelected" MinWidth="40" Grid.Row="2" Grid.Column="1" Content="Display" Click="displaySelected_Click" />
</Grid>
</Window>
Window1.xaml.cs
Here's the code-behind. Not much to it! Everything is accessed through the dataContext instance. There's no need to know control names, etc.
public partial class Window1 : Window
{
ComboViewModel dataContext = new ComboViewModel();
public Window1()
{
InitializeComponent();
dataContext.Items=new List<string>(new string[]{"First Item","Second Item"});
this.DataContext = dataContext;
}
private void displaySelected_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(String.Format("Selected item:\n\nIndex: {0}\nValue: {1}", dataContext.Index, dataContext.Value));
}
}
You can add business logic for populating models from a database, saving changes to a database, etc. When you alter the properties of the view model, the UI will automatically be updated.

WPF Usercontrol with custom properties

I want to outsource two images to a custom usercontrol, which should have two properties to be set, one for the source of each image.
But I ran into trouble with the datacontext, which isnt recognised correctly. It might also be a problem, that its the first time that I use dependency properties. Anyway, I hope you can figure out my thoughts and help me here, here comes the sourcecode:
MainViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private string _spielerL1;
private string _spielerL2;
public MainWindowViewModel()
{
SpielerL1 = System.IO.Directory.GetCurrentDirectory() + #"\Images\queen_of_clubs.png";
SpielerL2 = System.IO.Directory.GetCurrentDirectory() + #"\Images\queen_of_diamonds.png";
[...]
}
public string SpielerL1
{
get { return _spielerL1; }
private set
{
_spielerL1 = value;
OnPropertyChanged("SpielerL1");
}
}
public string SpielerL2
{
get { return _spielerL2; }
private set
{
_spielerL2 = value;
OnPropertyChanged("SpielerL2");
}
}
}
In my mainwindow view I am only instantiating the viewmodel and using the control with SourceLeft="{Binding SpielerL1}" and SourceRight="{Binding SpielerL2}"...
My control code behind looks like this (deleted sourceright to make it shorter):
public partial class HandControl
{
public HandControl()
{
InitializeComponent();
DataContext = this;
}
public string SourceLeft
{
get
{
return (string) GetValue(SourceLeftProperty);
}
set
{
SetValue(SourceLeftProperty, value);
}
}
public static readonly DependencyProperty SourceLeftProperty = DependencyProperty.Register("SourceLeft", typeof(string), typeof(HandControl), new PropertyMetadata(""));
}
And finally my usercontrol xaml, which isnt recognising the datacontext or atleast not showing my images:
<UserControl x:Class="FoolMe.Gui.Controls.HandControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="1"
Source="{Binding SourceLeft}" />
<Image Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Source="{Binding SourceRight}" />
</Grid>
</UserControl>
Since I havent done much with WPF and usercontrols yet, I have no clue, whats wrong. Without the usercontrol it has working fine, but outsourcing it like this, my window keeps "white".
Anyone got an idea, what went wrong?
You shouldn't set the DataContext of the UserControl to itself. However, your real problem comes from your Binding on the Image elements. You should use a RelativeSource Binding instead:
<Image Grid.Column="1" Source="{Binding SourceLeft, RelativeSource={RelativeSource
AncestorType={x:Type YourXmlNamespacePrefix:HandControl}}}" />
<Image Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Source="{Binding SourceRight,
RelativeSource={RelativeSource AncestorType={x:Type YourXmlNamespacePrefix:
HandControl}}}" />

ICommand in MVVM WPF

I'm having a look at this MVVM stuff and I'm facing a problem.
The situation is pretty simple.
I have the following code in my index.xaml page
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<view:MovieView ></view:MovieView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and in my index.xaml.cs
...
InitializeComponent();
base.DataContext = new MovieViewModel(ent.Movies.ToList());
....
and here is my MoveViewModel
public class MovieViewModel
{
readonly List<Movies> _m;
public ICommand TestCommand { get; set; }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new TestCommand(this);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
}
finally
here is my control xaml MovieView
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label VerticalAlignment="Center" Grid.Row="0" Grid.Column="0">Title :</Label><TextBlock VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" Text="{Binding Title}"></TextBlock>
<Label VerticalAlignment="Center" Grid.Row="1" Grid.Column="0">Director :</Label><TextBlock VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" Text="{Binding Director}"></TextBlock>
<Button Grid.Row="2" Height="20" Command="{Binding Path=TestCommand}" Content="Edit" Margin="0,4,5,4" VerticalAlignment="Stretch" FontSize="10"/>
</Grid>
So the problem I have is that if I set ItemsSource at Binding
it doesn't make anything
if I set ItemsSource="{Binding lm}"
it populates my itemsControl but the Command (Command="{Binding Path=TestCommand}" ) doesn't not work.
Of course it doesn't not work because TestCommand doesn't not belong to my entity object Movies.
So finally my question is,
what do I need to pass to the ItemsControl to make it working?
Thx in advance
As soon as your items are rendered, each item gets set to the DataContext of the specific row it represents, so you are no longer able to reference to your first DataContext.. Also, due to the fact that you are in a DataTemplate, your bindings will start working when there is need for that Template.. so in that case you need to look up your parent control through a RelativeSource binding...
Hope that explains some things..
Try implementing the INotifyPropertyChanged interface:
public class MovieViewModel : INotifyPropertyChanged
{
readonly List<Movies> _m;
private ICommand _testCommand = null;
public ICommand TestCommand { get { return _testCommand; } set { _testCommand = value; NotifyPropertyChanged("TestCommand"); } }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new TestCommand(this);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
}
What happens is that the TestCommand has a value, and the UI gets no notification that a change is taking place.. On controls you solve this problem using Dependency properties, on data object, you can use the INotifyPropertyChanged interface..
Secondly, the Movie objects have no reference to the parent object..
You can solve this problem in three different ways:
have a reference back to the model on Movie, and make the Bind path like so: ie.. if you property is named ParentMovieModel, then your Binding will be like:
{Binding Path=ParentMovieModel.TestCommand}
Make a binding based on ElementName like so: Seek up the parent control where you set your DataContext on, and give it a name: i.e. Root. Now create a binding based on the ElementName like so:
{Binding ElementName=Root, Path=DataContext.TextCommand}
Make a binding based on a RelativeSource like so: Seek up the parent control by type, and use the same path as the one above...
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type yourparentwindowtype}}, Path=DataContext.TextCommand}
Got it working
here is the thing
<ItemsControl DataContext="{Binding}" ItemsSource="{Binding lm}">
Command="{Binding Path=DataContext.TestCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
so the RelativeSource was the thing I've missed.
if somebody has a good explaination of this, I would be definitely happy.
//include namespace
using Microsoft.Practices.Composite.Wpf.Commands;
readonly List<Movies> _m;
public ICommand TestCommand { get; set; }
public MovieViewModel(List<Movies> m)
{
this.TestCommand = new DelegateCommand<object>(TestcommandHandler);
_m = m;
}
public List<Movies> lm
{
get
{
return _m;
}
}
void TestcommandHandler(object obj)
{
// add your logic here
}
}
What about <ItemsControl ItemsSource="{Binding Path=lm}"> ?
in the ItemsSource="{Binding Path=lm}"> case
the itemsControl works well but I complety bypass my MovieViewModel
and I got this in the output window
System.Windows.Data Error: 39 : BindingExpression path error: 'TestCommand' property not found on 'object' ''Movies' (HashCode=1031007)'. BindingExpression:Path=TestCommand; DataItem='Movies' (HashCode=1031007); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
Movies is my entity object and owns only the Title and Director properties

Categories

Resources