What I'm doing:
On my DataGrid I have a context menu to add Template items. The MenuItems are created dynamically using the ItemsSource property of the parent MenuItem. The ItemsSource is an ObservableCollection of my template objects. I want to get the Header of the dynamic MenuItems from the collection object properties, but execute a command from my main ViewModel. The main ViewModel is bound to the DataContext of the root Grid.
My issue:
The MenuItems are created properly and I can also bind the header to a property of an object in the collection. But the Command binding does not work. I can see an error in the output window:
"System.Windows.Data Error: 4 : Cannot find source for binding with reference..."
Here is my code reduced to the issue (using MVVM light):
MainWindow.xaml:
<Window x:Class="TapTimesGui.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="1000">
<!-- root grid -->
<Grid x:Name="RootGrid" DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<!-- Jobs overview -->
<DataGrid Grid.Column="0">
<DataGrid.ContextMenu>
<ContextMenu>
<!-- following works fine -->
<MenuItem Header="Basic command test" Command="{Binding AddTemplateJobCommand}" CommandParameter="Basic command test"/>
<MenuItem Header="Add template..." ItemsSource="{Binding JobTemplates}">
<MenuItem.ItemTemplate>
<DataTemplate>
<!-- following command does not work System.Windows.Data Error -->
<MenuItem Header="{Binding Name}" Command="{Binding ElementName=RootGrid, Path=DataContext.AddTemplateJobCommand}" CommandParameter="{Binding Name}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
<!--<MenuItem.ItemContainerStyle> also does not work
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"></Setter>
<Setter Property="Command" Value="{Binding ElementName=RootGrid, Path=DataContext.SaveDayToNewFileCommand}"></Setter>
</Style>
</MenuItem.ItemContainerStyle>-->
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace TapTimesGui
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
MainViewModel.cs:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using GalaSoft.MvvmLight.Messaging;
using System.Collections.ObjectModel;
using System.Windows.Input;
using TapTimesGui.Services;
namespace TapTimesGui.ViewModel
{
/// <summary>
/// ...
/// </summary>
public class MainViewModel : ViewModelBase
{
#region Properties and backing fields
/// <summary>
/// Templates for jobs
/// </summary>
public ObservableCollection<JobTemplate> JobTemplates
{
get
{
return _jobTemplates;
}
}
private readonly ObservableCollection<JobTemplate> _jobTemplates = new ObservableCollection<JobTemplate>();
#endregion Properties and backing fields
#region ICommand properties
public ICommand AddTemplateJobCommand { get; private set; }
#endregion ICommand properties
#region Constructors
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
// assign commands
AddTemplateJobCommand = new RelayCommand<string>(AddTemplateJob);
// populate data on start
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
//TODO test for templates
JobTemplate tmpJobTemplate = new JobTemplate();
tmpJobTemplate.Name = "Template 1";
tmpJobTemplate.Template = "TestCustomer1 AG";
JobTemplates.Add(tmpJobTemplate);
tmpJobTemplate = new JobTemplate();
tmpJobTemplate.Name = "Template 2";
tmpJobTemplate.Template = "TestCustomer2 AG";
JobTemplates.Add(tmpJobTemplate);
}
}
#endregion Constructors
#region Command methods
private void AddTemplateJob(string name)
{
//TODO implement
Messenger.Default.Send<NotificationMessage>(new NotificationMessage(name));
}
#endregion Command methods
}
}
ViewModelLocator.cs:
/*
In App.xaml:
<Application.Resources>
<vm:ViewModelLocator xmlns:vm="clr-namespace:TapTimesGui"
x:Key="Locator" />
</Application.Resources>
In the View:
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
You can also use Blend to do all this with the tool's support.
See http://www.galasoft.ch/mvvm
*/
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
//using Microsoft.Practices.ServiceLocation; TODO http://www.mvvmlight.net/std10 chapter known issues
using CommonServiceLocator;
using GalaSoft.MvvmLight.Messaging;
using System;
using System.Windows;
namespace TapTimesGui.ViewModel
{
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// </summary>
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
////if (ViewModelBase.IsInDesignModeStatic)
////{
//// // Create design time view services and models
//// SimpleIoc.Default.Register<IDataService, DesignDataService>();
////}
////else
////{
//// // Create run time view services and models
//// SimpleIoc.Default.Register<IDataService, DataService>();
////}
SimpleIoc.Default.Register<MainViewModel>();
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageHandler);
Messenger.Default.Register<Exception>(this, ExceptionMessageHandler);
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
private void NotificationMessageHandler(NotificationMessage message)
{
//MessageBox.Show(message.Notification);
System.Diagnostics.Debug.WriteLine(message.Notification);
MessageBox.Show(message.Notification);
}
private void ExceptionMessageHandler(Exception ex)
{
MessageBox.Show(ex.ToString(), "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
JobTemplate.cs:
using System.ComponentModel;
namespace TapTimesGui.Services
{
/// <summary>
/// Named TapTimesGui.Model.Job template
/// </summary>
/// <remarks>Can be used e.g. to save Templates of specific objects</remarks>
public class JobTemplate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
private string _name = "";
public string Template //Type was not string originally, it was if my Model object
{
get
{
//TODO proper cloning
return _template;
}
set
{
//TODO proper cloning
_template = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Template)));
}
}
private string _template = "Template";
}
}
Versions:
Visual Studio Professional 2015
.NET Framework 4.5.2
MvvmLight 5.4.1.1
CommonServiceLocator 2.0.5 (required for MvvmLight)
I already tried to find a solution for some time. Thanks in advance for your help.
You can access the ViewModelLocator inside the ItemTemplate as well.
<DataGrid Grid.Column="0">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Add template..." ItemsSource="{Binding JobTemplates}">
<MenuItem.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding Name}" Command="{Binding Source={StaticResource Locator}, Path=Main.AddTemplateJobCommand}" CommandParameter="{Binding Name}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
A more general approach without MVVM light could be using a binding proxy.
Related
I have list of strings that I want to use in several ComboBox controls in different tabs of a TabControl. The list of strings is in a public ObservableCollection. The problem is, that I can't get this collection to show in the <Window.Resources> section of the XAML file. Here is the code in a clean new application:
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace APX_Interface
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<String> MyStringList; // not initialized yet
public class NameList : ObservableCollection<String>
{
public NameList() : base()
{
Add("Willam");
Add("Isak");
Add("Victor");
Add("Jules");
}
}
}
}
Here is the XAML:
Window x:Class="APX_Interface.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:APX_Interface"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MyStringList x:Key="Strings"/>
<local:NameList x:Key="Names"/>
</Window.Resources>
<Grid>
</Grid>
The only thing that shows up for autocomplete when I type local: is App. Compiling the code I get several errors like this:
Error The tag 'MyStringList' does not exist in XML namespace 'clr-namespace:APX_Interface'.
Error XDG0008 The name "MyStringList" does not exist in the namespace "clr-namespace:APX_Interface".
Error XLS0414 The type 'local:MyStringList' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built.
The same errors for the class NameList
After looking at many examples, and other discussions here I can't pinpoint what I'm missing.
Thanks for all the suggestions, unfortunately we haven't resolved this issue yet.
I'm posting here the code with the changes suggested and my comments:
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace APX_Interface
{
public class NameList : ObservableCollection<String>
// This class is now THE ONLY thing visible in <Window.Resources>
// but not any instance of the class.
{
public NameList() : base()
{
}
}
public partial class MainWindow : Window
{
// This instance of the class is NOT visible in <Window.Resources> ???
public NameList MyNames { get { return _names;} }
// nor is this
public ObservableCollection<String> MyNumbers { get { return _numbers; } }
// nope
public NameList _names = null;
//Neither this one
public ObservableCollection<String> _numbers = null;
public MainWindow()
{
InitializeComponent();
// this doesn't make any difference
DataContext = this;
_names = new NameList();
_names.Add("fellow"); // populate dynamically
var _nr = new string[] // static strings
{
"One",
"Two",
"etc.",
};
_numbers = new ObservableCollection<string>(_nr);
}
}
}
A few examples of accessing the collection in XAML and from Code Behind.
First example: A collection in a static property.
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{x:Static local:MyValues.NameList}" />
<StackPanel Grid.Column="1">
<TextBox x:Name="tbNew"/>
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
</Grid>
Code Behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
MyValues.NameList.Add(tbNew.Text);
}
Second example: a collection instance is created in the XAML resources.
XAML:
<Window.Resources>
<local:StringCollectionINCC x:Key="Strings">
<sys:String>Willam</sys:String>
<sys:String>Isak</sys:String>
<sys:String>Victor</sys:String>
<sys:String>Jules</sys:String>
</local:StringCollectionINCC>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Mode=OneWay, Source={StaticResource Strings}}" />
<StackPanel Grid.Column="1">
<TextBox x:Name="tbNew"/>
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
</Grid>
Code Behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
StringCollectionINCC list = (StringCollectionINCC)Resources["Strings"];
list.Add(tbNew.Text);
}
Third example (best): creating collections in the MVVM pattern.
To create a team, an additional class is used that implements ICommand:
/// <summary>Executing Delegate.</summary>
/// <param name="parameter">Command parameter.</param>
public delegate void ExecuteHandler(object parameter);
/// <summary>CanExecuting Delegate.</summary>
/// <param name="parameter">Command parameter.</param>
/// <returns><see langword="true"/> - if command execution is allowed.</returns>
public delegate bool CanExecuteHandler(object parameter);
/// <summary>A class implementing the ICommand interface for creating WPF commands.</summary>
public class RelayCommand : ICommand
{
private readonly CanExecuteHandler _canExecute = CanExecuteDefault;
private readonly ExecuteHandler _execute;
private readonly EventHandler _requerySuggested;
public event EventHandler CanExecuteChanged;
/// <summary>The constructor of the command.</summary>
/// <param name="execute">Command Executable Method.</param>
/// <param name="canExecute">Team Status Method.</param>
public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
_requerySuggested = (o, e) => Invalidate();
CommandManager.RequerySuggested += _requerySuggested;
}
/// <summary>The method of invoking an event about a change in command status.</summary>
public void Invalidate() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute.Invoke(parameter);
public void Execute(object parameter) => _execute?.Invoke(parameter);
/// <summary>Default CanExecute Method/</summary>
/// <param name="parameter">>Command parameter.</param>
/// <returns>Is always <see langword="true"/>.</returns>
public static bool CanExecuteDefault(object parameter) => true;
}
ViewModel with collection and command:
/// <summary>ViewModel</summary>
public class MainVM
{
public ObservableCollection<string> NameList { get; }
= new ObservableCollection<string>()
{
"Willam",
"Isak",
"Victor",
"Jules"
};
private RelayCommand _addCommand;
public RelayCommand AddCommand => _addCommand
?? (_addCommand = new RelayCommand(AddMethod, AddCanMethod));
/// <summary>A method that checks that a parameter can be cast to
/// a string and that string is not empty.</summary>
/// <param name="parameter">Command parameter.</param>
/// <returns><see langword="true"/> - if the conditions are met.</returns>
private bool AddCanMethod(object parameter)
=> parameter is string val
&& !string.IsNullOrWhiteSpace(val);
/// <summary>Method to add a value to a collection.</summary>
/// <param name="parameter">Valid command parameter.</param>
private void AddMethod(object parameter)
=> NameList.Add((string) parameter);
}
Locator - A commonly used solution for accessing all ViewModel or other data containers:
/// <summary>Contains all ViewModel. In this case, only one MainVM.</summary>
public class Locator
{
public MainVM MainVM { get; } = new MainVM();
}
XAML App - here it is more convenient to create resources that may be needed in different Windows:
<Application.Resources>
<local:Locator x:Key="Locator"/>
</Application.Resources>
XAML Windows: setting ViewModel in a DataContext and binding properties of elements.
<Window ....
DataContext="{Binding MainVM, Mode=OneWay, Source={StaticResource Locator}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding NameList}" />
<StackPanel Grid.Column="1">
<TextBox x:Name="tbNew"/>
<Button Content="Add"
Command="{Binding AddCommand, Mode=OneWay}"
CommandParameter="{Binding Text, ElementName=tbNew}"/>
</StackPanel>
</Grid>
</Window>
If you need to use local:NameList then you have to do as the answer above. And if you need to use MyStringList property, then you have to do like below:
public partial class MainWindow : Window
{
public ObservableCollection<string> _myStringList=new ObservableCollection<string>(); // not initialized yet
public ObservableCollection<string> MyStringList
{
get
{
return _myStringList;
}
set
{
_myStringList = value;
}
}
public MainWindow()
{
InitializeComponent();
fillCombo();
DataContext = this;
}
public void fillCombo()
{
MyStringList.Add("Willam");
MyStringList.Add("Isak");
MyStringList.Add("Victor");
MyStringList.Add("Jules");
}
}
in XML:
<ComboBox ItemsSource="{Binding MyStringList}" Name="comboBox1" Margin="34,56,0,0"
VerticalAlignment="Top"
Width="200"/>
Note: you can also create a usercontrol or customcontrol in your project and then design the control with your desired element. After that you can use created control whichever XML pages you want.
There are several solutions, but to choose a specific one, you need to give more details of the task.
If you need a simple collection of strings, then you don’t need to create your own class.
Better to use, as #Clements wrote, StringCollection.
If you need the ability to change the collection (INotifyCollectionChanged interface), you can add your own class.
But you need to build it in the namespace, and not embed it in another class.
Initializing it with values is better in XAML resources, rather than in C#-code.
namespace APX_Interface
{
public class StringCollectionINCC : ObservableCollection<string> { }
}
If there is a need to create a global instance accessible from anywhere in the application, then you can make a static property that provides this instance.
namespace APX_Interface
{
public static class MyValues
{
public static StringCollectionINCC NameList { get; }
= new StringCollectionINCC()
{
"Willam",
"Isak",
"Victor",
"Jules"
};
}
}
You can get it in XAML like this:
<Window.Resources>
<local:StringCollectionINCC x:Key="Strings">
<sys:String>Willam</sys:String>
<sys:String>Isak</sys:String>
<sys:String>Victor</sys:String>
<sys:String>Jules</sys:String>
</local:StringCollectionINCC>
<x:Static Member="local:MyValues.NameList" x:Key="Names"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Mode=OneWay, Source={StaticResource Strings}}" />
<ListBox Grid.Column="1" ItemsSource="{Binding Mode=OneWay, Source={StaticResource Names}}" />
</Grid>
You can also immediately, without resources, set a link to a static instance:
<ListBox Grid.Column="1" ItemsSource="{x:Static local:MyValues.NameList}" />
You have declared NameList as a nested class inside the MainWindow class. Move it out of there.
Besides that, you don't need to declare a MyStringList field in MainWindow, and NameList doesn't need to be an ObservableCollection, since you apparently never add or remove elements to/from the collection after it was initialized.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class NameList : List<String>
{
public NameList()
{
Add("Willam");
Add("Isak");
Add("Victor");
Add("Jules");
}
}
Now use it like this:
<Window.Resources>
<local:NameList x:Key="Names"/>
</Window.Resources>
...
<ComboBox ItemsSource="{StaticResource Names}" .../>
Instead of declaring a NameList class in code, you may as well directly declare a string list resource in XAML.
Add XAML namespace declarations like these:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:coll="clr-namespace:System.Collections.Specialized;assembly=System"
and this resource:
<coll:StringCollection x:Key="Names">
<sys:String>Willam</sys:String>
<sys:String>Isak</sys:String>
<sys:String>Victor</sys:String>
<sys:String>Jules</sys:String>
</coll:StringCollection>
EDIT: In case you want to be able to manipulate the string collection in code behind, declaring a string list resource is the wrong apprach.
Instead, declare a view model class with a NameList property
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> NameList { get; }
= new ObservableCollection<string>();
}
and assign it to the DataContext of the MainWindow
private readonly ViewModel viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = viewModel;
viewModel.NameList.Add("Willam");
viewModel.NameList.Add("Isak");
viewModel.NameList.Add("Victor");
viewModel.NameList.Add("Jules");
}
In XAML, bind the ComboBox like
<ComboBox ItemsSource="{Binding NameList}" .../>
EDIt2: Another, very simple approach may be a static member, e.g. in the MainWindow class, like
public partial class MainWindow : Window
{
...
public static List<string> NameList { get; } = new List<string>
{
"Willam", "Isak", "Victor", "Jules"
};
}
which would be used like this:
<ComboBox ItemsSource="{x:Static local:MainWindow.NameList}" .../>
I wanted to embed a winform chart control in a WPF window which shall be bound to an observablecollection filled by entering data in a WPF DataGrid.
The observablecollection is needed because i fill it by using a WPF-DataGrid in which i can insert or update data.
So i added to my WPF-project the following dependencies:
- System.Windows.Forms
- System.Windows.Forms.DataVisualization
For a first test i hardcoded in the constructor of the WPF window some data in the observablecollection and bound the chart control.
In that case the display in the chart works fine.
But in the final version i want to insert and/or update data in the DataGrid and the chart shall display that data all at once.
Is it possible to manage that?
Here is the code for the window and the classes as example.
The WPF window MainWindow.xaml:
<Window x:Class="StepFunctions.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:winformchart="clr-namespace:System.Windows.Forms.DataVisualization.Charting;assembly=System.Windows.Forms.DataVisualization"
xmlns:local="clr-namespace:StepFunctions"
mc:Ignorable="d"
Title="StepFunctions"
Height="350"
Width="525">
<Grid x:Name="maingrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--DataGrid for insert and update of step function data-->
<DataGrid x:Name="grd_stepdata"
Grid.Row="0"
Grid.Column="0"
Margin="5"
AutoGenerateColumns="False"
CanUserAddRows="True"
RowEditEnding="grd_stepdata_RowEditEnding"
ItemsSource="{Binding StepDataSource, NotifyOnSourceUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="col_LowerComparer" Binding="{Binding LowerComparer, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower comparer"/>
<DataGridTextColumn x:Name="col_LowerBound" Binding="{Binding LowerBound, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Lower bound"/>
<DataGridTextColumn x:Name="col_StepValue" Binding="{Binding StepValue, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Value"/>
</DataGrid.Columns>
</DataGrid>
<!--Chart for displaying the step function data-->
<WindowsFormsHost x:Name="host"
Grid.Row="0"
Grid.Column="1"
Margin="5">
<winformchart:Chart x:Name="myWinformChart"
Dock="Fill">
<winformchart:Chart.Series>
<winformchart:Series Name="series" ChartType="Line"/>
</winformchart:Chart.Series>
<winformchart:Chart.ChartAreas>
<winformchart:ChartArea/>
</winformchart:Chart.ChartAreas>
</winformchart:Chart>
</WindowsFormsHost>
<!--Button for test-->
<Button x:Name="btn_do"
Grid.Row="2"
Grid.Column="0"
Margin="5"
Click="btn_do_Click">Do it</Button>
</Grid>
</Window>
The code-behind of MainWindow.xaml:
using StepFunctions.ViewModels;
using System.Windows;
using System.Windows.Controls;
namespace StepFunctions
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowViewModel vm = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
// These lines are just for the first test.
// Normally these lines would be out-commented.
AddStepdata();
ChartDataRefresh();
}
// When the user leaves a DataGrid-row after insert or update the chart shall be refreshed.
private void grd_stepdata_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
ChartDataRefresh();
}
private void AddStepdata()
{
vm.StepDataSource.Add(new StepData("<", 10, 1));
vm.StepDataSource.Add(new StepData("<", 100, 2));
vm.StepDataSource.Add(new StepData("<", 1000, 3));
}
private void ChartDataRefresh()
{
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
}
/// <summary>
/// For testing the refresh of the chart after the window was loaded.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_do_Click(object sender, RoutedEventArgs e)
{
AddStepdata();
ChartDataRefresh();
}
}
}
The viewmodel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace StepFunctions.ViewModels
{
public class MainWindowViewModel : INotifyPropertyChanged
{
/// <summary>
/// Eventhandler for signalising that a property has changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<StepData> stepdataSource = new ObservableCollection<StepData>();
public ObservableCollection<StepData> StepDataSource
{
get { return stepdataSource; }
set
{
stepdataSource = value;
RaisePropertyChanged("StepDataSource");
}
}
/// <summary>
/// Informs the target which is bound to a property, that it's source was changed and that it shall update.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And finally the class StepData which is the base for the observablecollection:
namespace StepFunctions.ViewModels
{
/// <summary>
/// Class for data of stepfunctions.
/// </summary>
public class StepData
{
/// <summary>
/// The constructor.
/// </summary>
public StepData()
{
// Do nothing
}
public StepData(string lowerComparer, double lowerBound, double stepValue)
{
LowerComparer = lowerComparer;
LowerBound = lowerBound;
StepValue = stepValue;
}
public string LowerComparer { get; set; }
public double LowerBound { get; set; }
public double StepValue { get; set; }
}
}
I got it!
The chart has to be generated in code-behind, not in the XAML.
So the method ChartDataRefresh has to look like that:
private void ChartDataRefresh()
{
Chart myWinformChart = new Chart();
myWinformChart.Dock = System.Windows.Forms.DockStyle.Fill;
Series mySeries = new Series("series");
mySeries.ChartType = SeriesChartType.Line;
myWinformChart.Series.Add(mySeries);
ChartArea myArea = new ChartArea();
myWinformChart.ChartAreas.Add(myArea);
myWinformChart.DataSource = vm.StepDataSource;
myWinformChart.Series["series"].XValueMember = "LowerBound";
myWinformChart.Series["series"].YValueMembers = "StepValue";
host.Child = myWinformChart;
}
While entering data in the WPF-DataGrid the Winform chart control is refreshed and the data displayed as a line for checking if the given data is correct.
Hi i' m trying to create a custom TabItem with delete Button,i want to bind my viewmodel command to my custom Dependency Property 'DeleteCommandProperty' . Can someone tell me what i'm doing wrong?
my Custom TabControl :
/// <summary>
/// TabControl withCustom TabItem
/// </summary>
public class MyTabControl:TabControl
{
/// <summary>
/// TabItem override
/// </summary>
/// <returns></returns>
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
my Custom TabItem class :
/// <summary>
/// Custom TabItem
/// </summary>
public class MyTabItem:TabItem
{
/// <summary>
/// Delete Command
/// </summary>
public static DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
"DeleteCommand",typeof(ICommand),typeof(MyTabItem));
/// <summary>
/// Delete
/// </summary>
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
}
when i bind the DeleteCommand directly like this my Command in my ViewModel is executed
<customControls:MyTabControl>
<customControls:MyTabItem Header="Test" DeleteCommand="{Binding DeleteStudiengangCommand}" Template="{DynamicResource MyTabItemControlTemplate}"/>
</customControls:MyTabControl>
bu when try to bind the deleteCommand via style like this but it doesn't work :
<Style TargetType="customControls:MyTabItem">
<Setter Property="Template" Value="{DynamicResource MyTabItemControlTemplate}"/>
<Setter Property="DeleteCommand" Value="{Binding MyDeleteCommand}"/>
</Style>
<customControls:MyTabControl ItemsSource="{Binding MyList}" SelectedItem="{Binding SelectedItem}" SelectedIndex="0">
<customControls:MyTabControl.ContentTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Value}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</customControls:MyTabControl.ContentTemplate>
</customControls:MyTabControl>
TL;DR
I suspect your DataContext contains your current item from MyList (whatever it is), so the style setter cannot access the MyDeleteCommand property as you planned. Look at the visual studio debugger log, whether a binding exception is logged there.
Example that evolved from a working code until it happened to reveal a possible problem explanation.
It seems you have some difficulties in reducing your example, so the only thing I can offer you based on the information you provide is a small working example with the Style and TemplateBinding approach which you can use as a base to identify your real problem. Edit: The explanation at the end might actually be the answer to your question.
Note you may have to change the namespaces to match with your project setup.
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<Window.Resources>
<!-- Header template -->
<ControlTemplate x:Key="MyTabItemControlTemplate" TargetType="{x:Type local:MyTabItem}">
<!-- Some text and the command button with template binding -->
<StackPanel Orientation="Horizontal" Background="Aquamarine" Margin="3">
<ContentPresenter Content="{TemplateBinding Header}" VerticalAlignment="Center" Margin="2"/>
<Button Content="Delete" Command="{TemplateBinding DeleteCommand}" Margin="2"/>
</StackPanel>
</ControlTemplate>
<!-- Setting the control template and assigning the command implementation -->
<Style TargetType="{x:Type local:MyTabItem}">
<Setter Property="Template" Value="{DynamicResource MyTabItemControlTemplate}"/>
<Setter Property="DeleteCommand" Value="{Binding MyDeleteCommand}"/>
<Setter Property="Header" Value="Default Header Text"/>
</Style>
</Window.Resources>
<Grid>
<local:MyTabControl ItemsSource="{Binding MyTabItemList}"/>
</Grid>
</Window>
MainWindow.xaml.cs
/// <summary>
/// TabControl withCustom TabItem
/// </summary>
public class MyTabControl : TabControl
{
/// <summary>
/// TabItem override
/// </summary>
protected override DependencyObject GetContainerForItemOverride()
{
return new MyTabItem();
}
}
public class MyTabItem : TabItem
{
/// <summary>
/// Delete Command
/// </summary>
public static DependencyProperty DeleteCommandProperty = DependencyProperty.Register(
"DeleteCommand", typeof(ICommand), typeof(MyTabItem));
/// <summary>
/// Delete
/// </summary>
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
}
public class MyCommand : ICommand
{
public void Execute(object parameter)
{
MessageBox.Show("Hello WPF", "Message");
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged { add { } remove { } }
}
public class MyContext
{
public ICommand MyDeleteCommand { get; set; }
public List<object> MyTabItemList { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var list = new List<object>();
list.Add(new TextBlock() { Text = "Test 1" });
list.Add(new MyTabItem() { Content = "Test Content 2", Header = "Test Header 2" });
list.Add(new TabItem() { Content = "Test Content 3", Header = "Test Header 3" });
this.DataContext = new MyContext()
{
MyTabItemList = list,
MyDeleteCommand = new MyCommand()
};
}
}
Summary of the example:
You see three different tabs, each with its unique build and appearance:
The tab item is created from the GetContainerForItemOverride method, the style setter for the Header property specifies the text that appears within the header. The MyDeleteCommand binding does not work!
The tab item is provided as MyTabItem with header and content value. The style setter for the Header property does not apply, because the property is explicitely set. The style setter for the DeleteCommand property binds to the MyDeleteCommand property from MyContext.
The tab item is provided as TabItem and because there is no override of MyTabControl.IsItemItsOwnContainerOverride, this is accepted as a control item of MyTabControl. The whole MyTabItem template does not apply.
The debugging output inside visual studio gives a hint about the underlying problem for the first tab item:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyDeleteCommand' property not found on 'object' ''TextBlock' (Name='')'. BindingExpression:Path=MyDeleteCommand; DataItem='TextBlock' (Name=''); target element is 'MyTabItem' (Name=''); target property is 'DeleteCommand' (type 'ICommand')
The reason is, that the current tab item content becomes the new local DataContext in this scenario unlike the second tab item, where the item itself is accepted as the container.
A solution could be to ensure usage of the propper context on the command binding:
Supposed you give some suitable parent element a name x:Name="_this", then you can access the parent DataContext.
<Setter Property="DeleteCommand" Value="{Binding DataContext.MyDeleteCommand,ElementName=_this}"/>
I am working with a team on LoB application. We would like to have a dynamic Menu control, which creates the menu based on the logged in user profile. In previous development scenarios (namely ASP.NET) we use to iterate through data which describes collection and generate MenuItem dynamically. In MVVM how would I do this? Can I separate XAML view from ViewModel which describes menu elements?
Solution:
With inputs from commentators I were able to bind Menu dynamically with the data from ViewModel. This article was of great help too.
XAML:
<HierarchicalDataTemplate DataType="{x:Type self:Menu}" ItemsSource="{Binding Path=Children, UpdateSourceTrigger=PropertyChanged}">
<ContentPresenter Content="{Binding Path=MenuText}" RecognizesAccessKey="True"/>
</HierarchicalDataTemplate>
[...]
<Menu Height="21" Margin="0" Name="mainMenu" VerticalAlignment="Top" HorizontalAlignment="Stretch"
ItemsSource="{Binding Path=MenuItems, UpdateSourceTrigger=PropertyChanged}" ItemContainerStyle="{StaticResource TopMenuItems}">
<Menu.Background>
<ImageBrush ImageSource="/Wpf.Modules;component/Images/MenuBg.jpg" />
</Menu.Background>
</Menu>
Menu data class:
public class Menu : ViewModelBase
{
public Menu()
{
IsEnabled = true;
Children = new List<Menu>();
}
#region [ Menu Properties ]
private bool _isEnabled;
private string _menuText;
private ICommand _command;
private IList<Menu> _children;
public string MenuText
{
get { return _menuText; }
set
{
_menuText = value;
base.OnPropertyChanged("MenuText");
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
base.OnPropertyChanged("IsEnabled");
}
}
public ICommand Command
{
get { return _command; }
set
{
_command = value;
base.OnPropertyChanged("Command");
}
}
public IList<Menu> Children
{
get { return _children; }
set
{
_children = value;
}
}
#endregion
}
Try something like this:
public class MenuItemViewModel
{
public MenuItemViewModel()
{
this.MenuItems = new List<MenuItemViewModel>();
}
public string Text { get; set; }
public IList<MenuItemViewModel> MenuItems { get; private set; }
}
Assume that your DataContext has a property called MenuItems which is a list of MenuItemViewModel. Something like this should work, then:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:MenuItemViewModel}"
ItemsSource="{Binding Path=MenuItems}">
<ContentPresenter Content="{Binding Path=Text}" />
</HierarchicalDataTemplate>
</Window.Resources>
<DockPanel>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />
<Grid />
</DockPanel>
</Window>
This should get you where you are going
<UserControl x:Class="WindowsUI.Views.Default.MenuView"
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:ViewModels="clr-namespace:WindowsUI.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=DisplayName}"/>
<Setter Property="Command" Value="{Binding Path=Command}"/>
</Style>
<HierarchicalDataTemplate
DataType="{x:Type ViewModels:MenuItemViewModel}"
ItemsSource="{Binding Path=Items}">
</HierarchicalDataTemplate>
</UserControl.Resources>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=Items}"/>
Note that in my example, my menu Item has a property of type ICommand called Command.
This solution doesn't need any code in code behind and that makes it simpler solution.
<Menu>
<MenuItem ItemsSource="{Binding Path=ChildMenuItems}" Header="{Binding Path=Header}">
<MenuItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:MenuItemViewModel}" ItemsSource="{Binding ChildMenuItems}">
<MenuItem Header="{Binding Path=Header}" Command="{Binding Path=Command}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type vm:SeparatorViewModel}">
<Separator>
<Separator.Template>
<ControlTemplate>
<Line X1="0" X2="1" Stroke="Black" StrokeThickness="1" Stretch="Fill"/>
</ControlTemplate>
</Separator.Template>
</Separator>
</DataTemplate>
</MenuItem.Resources>
</MenuItem>
</Menu>
And MenuItem is represented as:
public class MenuItemViewModel : BaseViewModel
{
/// <summary>
/// Initializes a new instance of the <see cref="MenuItemViewModel"/> class.
/// </summary>
/// <param name="parentViewModel">The parent view model.</param>
public MenuItemViewModel(MenuItemViewModel parentViewModel)
{
ParentViewModel = parentViewModel;
_childMenuItems = new ObservableCollection<MenuItemViewModel>();
}
private ObservableCollection<MenuItemViewModel> _childMenuItems;
/// <summary>
/// Gets the child menu items.
/// </summary>
/// <value>The child menu items.</value>
public ObservableCollection<MenuItemViewModel> ChildMenuItems
{
get
{
return _childMenuItems;
}
}
private string _header;
/// <summary>
/// Gets or sets the header.
/// </summary>
/// <value>The header.</value>
public string Header
{
get
{
return _header;
}
set
{
_header = value; NotifyOnPropertyChanged("Header");
}
}
/// <summary>
/// Gets or sets the parent view model.
/// </summary>
/// <value>The parent view model.</value>
public MenuItemViewModel ParentViewModel { get; set; }
public virtual void LoadChildMenuItems()
{
}
}
The concrete MenuItems can be either instantiated directly or you could make your own SubTypes through inheritance.
I know this is an old post but I need this plus how to bind Commands.
As to Guge's question on how to bind Commands:
VMMenuItems is a property in my view model class of type
ObservableCollection<Menu>
and Menu is the class defined above. The MenuItem's Command Property is being bound to the Command Property of the Menu class.
In my view model class
Menu.Command = _fou
where
private ICommand _fou;
The xaml
<ListView.ContextMenu>
<ContextMenu ItemsSource="{Binding Path=VMMenuItems}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</ListView.ContextMenu>
If you're wondering how to do separators it's really quite easy.
The code below is part of my ViewModel. Since XAML uses reflection all I need to do is to return 'object' which can be a MenuItemViewModel, Separator, or (if for some wierd reason I needed to) an actual MenuItem.
I'm using yield to dynamically generate the items because it just seems to read better for me. Even though I'm using yield - if the items change I still need to raise a PropertyChanged event for "ContextMenu" as usual but I don't unnecessarily generate the list until it's needed.
public IEnumerable<object> ContextMenu
{
get
{
// ToArray() needed or else they get garbage collected
return GetContextMenu().ToArray();
}
}
public IEnumerable<object> GetContextMenu()
{
yield return new MenuItemViewModel()
{
Text = "Clear all flags",
};
// adds a normal 'Separator' menuitem
yield return new Separator();
yield return new MenuItemViewModel()
{
Text = "High Priority"
};
yield return new MenuItemViewModel()
{
Text = "Medium Priority"
};
yield return new MenuItemViewModel()
{
Text = "Low Priority"
};
yield break;
}
I am writing an application for Windows Phone 8.1, and I wanted to use a flyout on listView item. Because I am doing my best to write nice app, I am trying to use MVVM pattern and resource dictionaries with templates insead of all xaml in one page.
However, I can't bind my MenuFlyoutItem Command - it seems like it doesn't see the datacontext of the page, or it has some other dataContext. Here is some code:
1) My template in a separate resource dictionary:
<Grid Margin="0, 0, 0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:EmptyDateConverter x:Key="EmptyDateConverter" />
</Grid.Resources>
<i:Interaction.Behaviors>
<icore:EventTriggerBehavior EventName="Holding">
<converters:OpenMenuFlyoutAction />
</icore:EventTriggerBehavior>
</i:Interaction.Behaviors>
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="AddToCalendarMenuItem" Command="{Binding AddToCalendar}" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<Image Grid.Column="0" Source="{Binding ThumbnailUri}"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource ListItemTitle}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock x:Uid="DvdReleaseDate" />
<TextBlock Text="{Binding DvdRelease, Converter={StaticResource EmptyDateConverter}}" />
</StackPanel>
</StackPanel>
</Grid>
2) And here is the list view:
<ListView Grid.Row="1" x:Name="SearchListView"
ItemsSource="{Binding SearchList}"
ItemTemplate="{StaticResource SearchListTemplate}" SelectionChanged="NavigateToMovieDetails" />
My ViewModel is a static kind of singleton in the app.xaml.cs
I've tried to create a new instance of the VM in xaml, but it didn't work - maybe I was doing smth wrong.
I would really appreciate Your help! Thanks in advance.
Best regards,
Roman
If you stick with that <ItemTemplate> you will have to have a Command per every Model in your ViewModel, which is not ideal.
To set a single Command and have a CommandParameter see this SO Tutorial I made it is too much code to type here:
Implement a ViewModel Single Command with CommandParamater
#Chubosaurus Software following your approach I came up with this.
Here is a ListView bound to a list of todo items placed inside a DataTemplate which contains a TextBlock having a MenuFlyout to show edit, delete context menu kind of thing.
The key to bind the commands in the view model to the MenuFlyoutItem is to give the ListView a name and do element binding using the ElementName property in the Command to point to the ListView's name. To access the commands in our view model we've to go through the ListView's DataContext and bind it to a command on it because the DataContext of the MenuFlyoutItem is an item in the ItemsSource
The MainPage.xaml
<Page
x:Class="UWA.MenuFlyout.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWA.MenuFlyout"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UWA.MenuFlyout.ViewModels"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:common="using:UWA.MenuFlyout.Core"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Margin="24,24">
<ListView x:Name="Todos" ItemsSource="{Binding Todos}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Action}">
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="edit"
Command="{Binding ElementName=Todos, Path=DataContext.EditTodo}"
CommandParameter="{Binding}"/>
<MenuFlyoutItem Text="delete"
Command="{Binding ElementName=Todos, Path=DataContext.DeleteTodo}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RightTapped">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
The MainPage.xaml.cs is where the DataContext of the MainPage is set.
namespace UWA.MenuFlyout
{
using UWA.MenuFlyout.ViewModels;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
this.DataContext = new MainViewModel();
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.
/// This parameter is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// TODO: Prepare page for display here.
// TODO: If your application contains multiple pages, ensure that you are
// handling the hardware Back button by registering for the
// Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
// If you are using the NavigationHelper provided by some templates,
// this event is handled for you.
}
}
}
The MainViewModel.cs containing the Todos which is an ObservableCollection type and the EditTodo and DeleteTodo commands.
namespace UWA.MenuFlyout.ViewModels
{
using System.Collections.ObjectModel;
using System.Windows.Input;
using UWA.MenuFlyout.Core;
using UWA.MenuFlyout.Models;
public class MainViewModel : BaseViewModel
{
private ICommand editTodo;
private ICommand deleteTodo;
public MainViewModel()
{
this.Todos = new ObservableCollection<TodoModel>
{
new TodoModel { Id = 1, Action = "Buy Milk", IsDone = true },
new TodoModel { Id = 2, Action = "Buy Groceries", IsDone = false }
};
}
public ObservableCollection<TodoModel> Todos { get; set; }
public ICommand EditTodo
{
get
{
if (this.editTodo == null)
{
this.editTodo = new RelayCommand(this.OnEditTodo);
}
return this.editTodo;
}
}
public ICommand DeleteTodo
{
get
{
if (this.deleteTodo == null)
{
this.deleteTodo = new RelayCommand(this.OnDeleteTodo);
}
return this.deleteTodo;
}
}
public void OnEditTodo(object parameter)
{
// perform edit here
var todo = parameter as TodoModel;
}
public void OnDeleteTodo(object parameter)
{
// perform delete here
var todo = parameter as TodoModel;
}
}
}
The Model
namespace UWA.MenuFlyout.Models
{
public class TodoModel
{
public int Id { get; set; }
public string Action { get; set; }
public bool IsDone { get; set; }
}
}
The BaseViewModel which implements the INotifyPropertyChanged.
namespace UWA.MenuFlyout.Core
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
A simple ICommand implementation.
namespace UWA.MenuFlyout.Core
{
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private Action<object> action;
public RelayCommand(Action<object> action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action(parameter);
}
}
}
The OpenMenuFlyoutAction which implements DependencyObject and IAction to open the MenuFlyout by using the Execute method on the IAction interface.
namespace UWA.MenuFlyout.Core
{
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
var frameworkElement = sender as FrameworkElement;
var flyoutBase = FlyoutBase.GetAttachedFlyout(frameworkElement);
flyoutBase.ShowAt(frameworkElement);
return null;
}
}
}