XAML Binding to a FrameworkElement in a UserControl - c#

I got a custom UserControl (MyControl) with several properties (which works just fine). I want a new property that let the page using the UserControl "paste in" some content to be shown direct in the UserControl - ex. a Path. I have tried; ContentPresenter, ContentControl, StackPanel, with no luck...
MyControl.xaml
<ContentControl Grid.Column="0" Content="{Binding MyContent, ElementName=root}"></ContentControl>
MyControl.xaml.cs
public object MyContent
{
get { return (object)GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(object), typeof(MyControl), new PropertyMetadata(null));
SomePage.xml
<mycontrols:MyControl x:Name="FavoritesButton">
<mycontrols:MyControl.MyContent>
<Path Data="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z" Stretch="Uniform" Fill="#FFFFFFFF" Width="50" Height="50" Margin="30"></Path>
</mycontrols:MyControl.MyContent>
</mycontrols:MyControl>

I have the following which works really well. (While it is somewhat against the principles of MVVM ... I still like to dynamically handle my user controls in a single frame area of the main window)
My MainWindow.xaml:
<!-- Main Frame -->
<Grid Grid.Column="1" Margin="10" Name="MainWindowFrameContent">
<ItemsControl ItemsSource="{Binding Path=MainWindowFrameContent}" >
<!-- This controls the height automatically of the user control -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
My MainViewModel.cs:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using myProject.View;
using myProject.Models;
namespace myProject.ViewModel
{
public class MainViewModel : ObservableObject
{
public MainViewModel() { }
// This handles adding framework (UI) elements to the main window frame
ObservableCollection<FrameworkElement> _MainWindowFrameContent = new ObservableCollection<FrameworkElement>();
public ObservableCollection<FrameworkElement> MainWindowFrameContent
{
get { return _MainWindowFrameContent; }
set { _MainWindowFrameContent = value; RaisePropertyChangedEvent("MainWindowFrameContent"); }
}
}
}
MainViewModel.cs is a "public class MainViewModel : ObservableObject". This allows me to implement "RaisePropertyChangedEvent" so that the binding will successfully update when I change the value of "MainWindowFrameContent".
My ObservableObject.cs:
using System.ComponentModel;
namespace myProject.ViewModel
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then when I want to add an item to the MainWindowFrameContent, I simply do the following within my MainViewModel.cs:
void _AddNewUserControl()
{
myUserControl hControl = new myUserControl();
MainWindowFrameContent.Clear(); // Clear the frame before displaying new content
MainWindowFrameContent.Add(hControl);
}
Then you can create as many user controls as you would like. Each command you want to display can have its own void _AddNewUserControl type method in the VM and it will display to the main window. Again, its a bit contrary to the MVVM framework, but it keeps this pretty clean from a code base.

I got it working... The solution was as follows:
MyControl.xaml
<ContentControl Content="{Binding Shape, ElementName=root}" />
MyControl.xaml.cs
public Shape Shape
{
get { return (Shape)GetValue(ShapeProperty); }
set { SetValue(ShapeProperty, value); }
}
public static readonly DependencyProperty ShapeProperty =
DependencyProperty.Register("Shape", typeof(Shape), typeof(MyControl), new PropertyMetadata(null));
SomePage.xml
<mycontrols:MyControl>
<mycontrols:MyControl.Shape>
<Path Data="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z" Style="{StaticResource PathStyle}" />
</mycontrols:MyControl.Shape>
</mycontrols:MyControl>

Related

With nested Bindings in C# WPF ItemsControls is it possible to find a DataContext's parent?

I am still fairly new to binding in WPF, and I am curious to know how to properly determine the parent of a given DataContext when it it's binding is nested.
I have created some sample code to reconstruct my problem. But unfortunately the INotifyPropertyChanged (which is required for the presentation layer to update) makes the constructors rather large, the data in this example code is simply:
The parents list contains:
While the presentation is laid out with the MainWindow presenting each parent in a ParentUserControl. Which in turn then presents the associated Children in a ChildUserControl.
With the code as follows, what is the best way for the code behind to identify the parent of the button that was pressed?
MainWindow.xaml:
<Window x:Class="BindingProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingProblem">
<StackPanel>
<ItemsControl ItemsSource="{Binding .}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:ParentUserControl></local:ParentUserControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
MainWindow.xaml.cs: (Also contains definitions for ParentClass and ChildClass)
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace BindingProblem
{
public partial class MainWindow : Window
{
public List<ParentClass> data = new List<ParentClass>(){
new ParentClass()
{
ParentName = "Parent A",
children = new List<ChildClass>(){
new ChildClass() {ChildName = "Child 1"},
new ChildClass() {ChildName = "Child 2"}
}
},
new ParentClass()
{
ParentName = "Parent B",
children = new List<ChildClass>(){
new ChildClass() {ChildName = "Child 3"},
new ChildClass() {ChildName = "Child 4"}
}
}
};
public MainWindow()
{
InitializeComponent();
this.DataContext = data;
}
}
public class ParentClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string p) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p)); }
private string _ParentName;
public string ParentName
{
get { return _ParentName; }
set { _ParentName = value; NotifyPropertyChanged("ParentName"); }
}
private List<ChildClass> _children;
public List<ChildClass> children
{
get { return _children; }
set { _children = value; NotifyPropertyChanged("children"); }
}
}
public class ChildClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string p)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
}
private string _ChildName;
public string ChildName
{
get { return _ChildName; }
set { _ChildName = value; NotifyPropertyChanged("ChildName"); }
}
}
}
ParentUserControl.xaml:
<UserControl x:Class="BindingProblem.ParentUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingProblem">
<StackPanel>
<TextBlock Text="{Binding ParentName}"></TextBlock>
<ItemsControl ItemsSource="{Binding children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:ChildUserControl></local:ChildUserControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
ParentUserControl.xaml.cs:
using System.Windows.Controls;
namespace BindingProblem
{
public partial class ParentUserControl : UserControl
{
public ParentUserControl()
{
InitializeComponent();
}
}
}
ChildUserControl.xaml:
<UserControl x:Class="BindingProblem.ChildUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Margin="20,0">
<TextBlock Text="{Binding ChildName}"></TextBlock>
<Button Content="Click Me" Click="Button_Click"></Button>
</StackPanel>
</UserControl>
ChildUserControl.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace BindingProblem
{
public partial class ChildUserControl : UserControl
{
public ChildUserControl()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine(((ChildClass)DataContext).ChildName);// this works to get my data for the child
// How would I find the ParentName Feild of the parent assosiated to this child.
}
}
}
You have several options of achieving this:
You can walk up the visual tree in code behind by recursively calling the method GetParent of the VisualTreeHelper until you get the type of ParentUserControl. You can then access its DataContext property.
You can bind to the parent’s DataContext in XAML with {Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type yourNamespace:ParentUserControl}}, Path=DataContext}. If you bind this to the Tag property of the button, you could then get the reference from code behind.
You can create a property on the child view model of the type ICommand. The instance of this command is then created by the parent view model and the parent view models injects a reference to itself when doing so. Then it sets the child‘s property to the new command object. I this approach you don‘t use event handlers on the ChildUserControl, but bind the Button‘s command property to the command of the child view model. That way the command code will have a reference to the parent view model.
One simple solution is, each child can be given ParentName, so your ChildClass can become
public class ChildClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string p)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
}
public string ParentName {get;set;}
private string _ChildName;
public string ChildName
{
get { return _ChildName; }
set { _ChildName = value; NotifyPropertyChanged("ChildName"); }
}
}
when you instantiate ChildClass to add as items to the parent's children, just provide them the name of parent in which they are being added,
and then in the Click Event
private void Button_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine(((sender as Button).DataContext as ChildClass).ParentName);
}

RelayCommand not getting the right Model

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.

Change image visibility from an Usercontrol on C# WPF

I am coding an application, its a quiz, I have a main Window where I load different UserControls (Pages). so my problem is that I have one image on the MainWindow, I want to change the Visibility of this image from Collapsed to Visible from one of the UserControls but with no luck...
Here is my MainWindow:
<Window x:Class="MuseonQuiz_v3.PageSwitcher"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="clr-namespace:MuseonQuiz_v3.Pages"
xmlns:k="http://schemas.microsoft.com/kinect/2013"
Title="MainWindow" Height="710" Width="1127" IsEnabled="True" DataContext="{Binding}" FontFamily="KaiTi" ResizeMode="NoResize" WindowStyle="None"
WindowStartupLocation="CenterScreen" WindowState="Maximized">
<Grid>
<Grid>
<k:KinectRegion Name="kinectRegion">
<ContentControl x:Name="mainContentControl"/>
</k:KinectRegion>
</Grid>
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Grid.Resources>
<k:KinectSensorChooserUI HorizontalAlignment="Center" VerticalAlignment="Top" Name="sensorChooserUi" />
<k:KinectUserViewer VerticalAlignment="Bottom" HorizontalAlignment="Center" k:KinectRegion.KinectRegion="{Binding ElementName=kinectRegion}" Height="600" Width="600" />
<Image Name="colorStreamImage" Width="640" Height="480" Visibility="Collapsed" HorizontalAlignment="Center" />
</Grid>
</Grid>
and this is my UserControl:
public partial class Selectie : UserControl, ISwitchable
{
string backgroundSelectie = "pack://application:,,,/MuseonQuiz_v3;component/Images/Selectie/selectie_background.jpg";
public Selectie()
{
InitializeComponent();
selectieBackground();
animatieButtons();
}
#region ISwitchable Members
public void UtilizeState(object state)
{
throw new NotImplementedException();
}
#endregion
}
My question is... how do I change the Visibility of the colorStreamImage that is located in the MainWindow from the UserControl... I have tried making an instance of the MainWindow, but that does not work, maybe I have to use some binding, but I am not sure, I appreciate any help you can provide!
As Clemens mentioned, your best bet is to go down the MVVM path. This is a good tutorial to get started In the Box – MVVM Training.
First, you can create a view model that implements INotifyPropertyChanged. In this case, you may want it to have at least one property of type Visibility.
public class MainViewModel : INotifyPropertyChanged
{
private Visibility _imageVisibility;
public Visibility ImageVisibility
{
get { return _imageVisibility; }
set { _imageVisibility = value; OnPropertyChanged("ImageVisibility"); }
}
private BitmapImage _imageSource;
public BitmapImage ImageSource{...}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler eventHandler = PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now you'll want to set this view model as the data context on the main window. To do this, Paul Stovell has a good post on the different approaches: http://paulstovell.com/blog/mvvm-instantiation-approaches. Once we set it on the main window, the Selectie element will inherit the data context. Using the simplest approach:
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
Your Image element might then bind to the property like this:
<Image Visibility="{Binding ImageVisibility, UpdateSourceTrigger=PropertyChanged}" Source="{Binding ImageSource}" Height="200" Width="200"></Image>
The Selectie element can now change the ImageVisbility property on the view model since it shares the same data context as MainWindow. (I used the code-behind as an example. You'll probably want to push as much of that logic out of the view and into the view model or further downstream)
public partial class Selectie : UserControl
{
public Selectie()
{
InitializeComponent();
}
private void Selectie_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (((MainViewModel)this.DataContext).ImageVisibility == System.Windows.Visibility.Visible)
((MainViewModel)this.DataContext).ImageVisibility = System.Windows.Visibility.Collapsed;
else
((MainViewModel)this.DataContext).ImageVisibility = System.Windows.Visibility.Visible;
}
}

Windows Phone 8.1 - Menu flyout item click command c#

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;
}
}
}

Dependency Property vs INotifyPropertyChanged in ViewModel for Windows 8 application

I have created blank C#/XAML Windows 8 application. Add simple XAML code:
<Page
x:Class="Blank.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel
Margin="0,150"
HorizontalAlignment="Center">
<TextBlock
x:Name="xTitle"
Text="{Binding Title, Mode=TwoWay}"/>
<Button Content="Click me!" Click="OnClick" />
</StackPanel>
</Grid>
</Page>
And the simple code in C# part:
public sealed partial class MainPage
{
private readonly ViewModel m_viewModel;
public MainPage()
{
InitializeComponent();
m_viewModel = new ViewModel
{
Title = "Test1"
};
DataContext = m_viewModel;
}
private void OnClick(object sender, RoutedEventArgs e)
{
m_viewModel.Title = "Test2";
}
}
Now I want to implement ViewModel. I have two way:
Use Dependency Property
Implement INotifyPropertyChanged
For first approach it is:
public class ViewModel : DependencyObject
{
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string)
, typeof(ViewModel)
, new PropertyMetadata(string.Empty));
}
For second it is:
public class ViewModel : INotifyPropertyChanged
{
private string m_title;
public string Title
{
get
{
return m_title;
}
set
{
m_title = value;
OnPropertyChanged("Title");
}
}
protected void OnPropertyChanged(string name)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I prefer the first way, because it allows use coerce (Silverlight for web and for WP7 doesn't have coerce functionality.. WinRT too.. but I'm still looking and hope) and looks more natural for me. But unfortunately, it works as OneTime for the first approach.
Could anybody explain to me why MS abandon using Dependency Property for implementing view model?
You should not be using a DependencyProperty in your ViewModel - you should only use them in your controls. You will never want to bind one ViewModel to another, also ViewModels do not need to persist their values nor provide default values, nor provide property metadata.
You should only use INotifyPropertyChanged in your ViewModels.

Categories