i have custom usercontrol, which must change some boolean variable in window and get value from some binding. But when i try get at first time value it equal to default value - null. And when i do action it`s work good.
<catel:UserControl x:Class="App.Shell.Controls.ToggleButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catel="http://catel.codeplex.com"
xmlns:debug="clr-namespace:System.Diagnostics;assembly=System">
<!-- Content -->
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="37" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Label
Margin="0"
Padding="0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="#010000"
Foreground="#FEFEFF"
FontSize="12">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Label>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsTurnedOn}" Value="True">
<Setter Property="Content" Value="Turn off" />
</DataTrigger>
<DataTrigger Binding="{Binding IsTurnedOn}" Value="False">
<Setter Property="Content" Value="Turn on" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Button>
<Button.InputBindings>
<MouseBinding Command="{Binding ToggleCmd}" MouseAction="LeftDoubleClick" />
</Button.InputBindings>
</Button>
</Grid>
</catel:UserControl>
with catel viewmodel
namespace App.Shell.Controls
{
using Catel.Data;
using Catel.MVVM;
using System;
using System.Windows.Input;
/// <summary>
/// UserControl view model.
/// </summary>
public class ToggleButtonViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ToggleButtonViewModel"/> class.
/// </summary>
public ToggleButtonViewModel()
{
ToggleCmd = new Command(OnToggleCmdExecute);
}
/// <summary>
/// Gets the title of the view model.
/// </summary>
/// <value>The title.</value>
public override string Title { get { return "ToggleButton"; } }
public Command ToggleCmd { get; private set; }
private void OnToggleCmdExecute()
{
IsTurnedOn = !IsTurnedOn;
}
public bool IsTurnedOn
{
get { return GetValue<bool>(IsTurnedOnProperty); }
set { SetValue(IsTurnedOnProperty, value); }
}
public static readonly PropertyData IsTurnedOnProperty = RegisterProperty("IsTurnedOn", typeof(bool), setParent: false, createDefaultValue: null);
// TODO: Register models with the vmpropmodel codesnippet
// TODO: Register view model properties with the vmprop or vmpropviewmodeltomodel codesnippets
// TODO: Register commands with the vmcommand or vmcommandwithcanexecute codesnippets
}
}
and code behind
namespace App.Shell.Controls
{
using Catel.Windows.Controls;
using System.Windows;
using System.Windows.Input;
using Catel.MVVM.Views;
using System;
using Catel.MVVM;
/// <summary>
/// Interaction logic for ToggleButton.xaml.
/// </summary>
public partial class ToggleButton : UserControl
{
/// <summary>
/// Initializes a new instance of the <see cref="ToggleButton"/> class.
/// </summary>
public ToggleButton()
{
InitializeComponent();
}
[ViewToViewModel(MappingType = ViewToViewModelMappingType.TwoWayViewWins)]
public bool IsTurnedOn
{
get { return (bool)GetValue(IsTurnedOnProperty); }
set { SetValue(IsTurnedOnProperty, value); }
}
public static readonly DependencyProperty IsTurnedOnProperty =
DependencyProperty.Register("IsTurnedOn", typeof(bool), typeof(ToggleButton), new PropertyMetadata(null));
}
}
i insert that control to my catel window
<ctrls:ToggleButton IsTurnedOn="{Binding Path=IndividualSpeechTimerState, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
When i try get property IsTurnedOn from UserControl at first time it equal to default value.
Since you defined [ViewToViewModel(MappingType = ViewToViewModelMappingType.TwoWayViewWins)], it'll always write the first time property of the view. This allows you to externally bind this value.
Make sure to set a valid value in your dependency property definition (e.g. true or false).
[ViewToViewModel(MappingType = ViewToViewModelMappingType.ViewModelToView)]
public bool IsTurnedOn
{
get { return (bool)GetValue(IsTurnedOnProperty); }
set { SetValue(IsTurnedOnProperty, value); }
}
public static readonly DependencyProperty IsTurnedOnProperty =
DependencyProperty.Register("IsTurnedOn", typeof(bool), typeof(ToggleButton), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsTurnedOnChanged));
private static void OnIsTurnedOnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var btn = d as ToggleButton;
var vm = btn.ViewModel as ToggleButtonViewModel;
if(vm.IsTurnedOn != btn.IsTurnedOn)
vm.IsTurnedOn = btn.IsTurnedOn;
}
Related
I have a class Data, that has a property IsDactaCorrect. This is the objects that I use in a ObservableCollection as data source of a datagrid.
I would like to use a cell style with the rows of the datagrid, so if the property is false, show a tooltip in the cell with the data error.
For that I would like to use a attached property that binds to the IsDataProperty. This attached property is used to communicate the value with the style that is defined in a xml resoruces file. Also this style use a converter to return the text of the tooltip.
The problem is that the value that arrives to to the converter is unset.
Also I have a the same idea with the tooltip of the headers of the datagrid. This works, but this case it binds to a property in the view model of the window, so it is a different case. In this case I want to bind to a property of a the object that is data source of the row.
The code is this:
The base view model:
using System.ComponentModel;
namespace TestStyleWithAttachedProperties.ViewModels
{
public abstract class BaseViewModel : INotifyPropertyChanging, INotifyPropertyChanged
{
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Administrative Properties
/// <summary>
/// Whether the view model should ignore property-change events.
/// </summary>
public virtual bool IgnorePropertyChangeEvents { get; set; }
#endregion
#region Public Methods
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
public virtual void RaisePropertyChangedEvent(string propertyName)
{
// Exit if changes ignored
if (IgnorePropertyChangeEvents) return;
// Exit if no subscribers
if (PropertyChanged == null) return;
// Raise event
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
/// <summary>
/// Raises the PropertyChanging event.
/// </summary>
/// <param name="propertyName">The name of the changing property.</param>
public virtual void RaisePropertyChangingEvent(string propertyName)
{
// Exit if changes ignored
if (IgnorePropertyChangeEvents) return;
// Exit if no subscribers
if (PropertyChanging == null) return;
// Raise event
var e = new PropertyChangingEventArgs(propertyName);
PropertyChanging(this, e);
}
#endregion
}
}
The view that has the datagrid:
<Window x:Class="TestStyleWithAttachedProperties.MainWindowView"
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:vm="clr-namespace:TestStyleWithAttachedProperties.ViewModels"
xmlns:ap="clr-namespace:TestStyleWithAttachedProperties.AttachedProperties"
xmlns:local="clr-namespace:TestStyleWithAttachedProperties"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="GUIResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/>
<DataGrid Name="dgdTest" Grid.Column="0" Margin="5,5,5,5"
ItemsSource="{Binding Items}"
AutoGenerateColumns="false">
<DataGrid.Columns>
<DataGridTextColumn Header="Price" Binding="{Binding Price}" Width="150"
ap:HeaderAttachedProperty.Tooltip="{Binding Path=DataContext.PriceTooltip, Source={x:Reference dummyElement}}"
HeaderStyle="{StaticResource DataGridColumnHeaderConTooltip}"
ap:CellWithErrorsAttachedProperty.TextoTooltip01="{Binding Path=Tooltip, Source={x:Reference dummyElement}}"
ap:CellWithErrorsAttachedProperty.TextoTooltip02="{Binding Path=ErrorDescription, Source={x:Reference dummyElement}}"
ap:CellWithErrorsAttachedProperty.EsDatoCorrecto="{Binding Path=IsDataCorrect}"
CellStyle="{StaticResource DataGridCellWithErrorsStyle}">
</DataGridTextColumn>
<DataGridTextColumn Header="Discount" Binding="{Binding Discount}" Width="150"
ap:HeaderAttachedProperty.Tooltip="Tooltip cabecera 2"
HeaderStyle="{StaticResource DataGridColumnHeaderConTooltip}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The view model:
using System.Collections.ObjectModel;
namespace TestStyleWithAttachedProperties.ViewModels
{
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel()
{
Items = new ObservableCollection<Data>();
Data miData01 = new Data()
{
Price = 1m,
Discount = 0m,
Tooltip = "Tooltip Data01",
ErrorDescription = "No errors",
IsDataCorrect = true,
};
Items.Add(miData01);
Data miData02 = new Data()
{
Price = 2m,
Discount = 10m,
Tooltip = "Tooltip Data02",
ErrorDescription = "No errors",
IsDataCorrect = true,
};
Items.Add(miData02);
Data miData03 = new Data()
{
Price = -1m,
Discount = 0m,
Tooltip = "Tooltip Data03",
ErrorDescription = "Price has to be greater than 0.",
IsDataCorrect = false,
};
Items.Add(miData03);
}
private ObservableCollection<Data> _items;
public ObservableCollection<Data> Items
{
get { return _items; }
set
{
_items = value;
base.RaisePropertyChangedEvent(nameof(Items));
}
}
private string _priceTooltip = "Tooltip for Price column";
public string PriceTooltip
{
get { return _priceTooltip; }
set { _priceTooltip = value; }
}
}
}
Then converter that I want to use in the stlye:
using System;
using System.Globalization;
using System.Windows.Data;
namespace TestStyleWithAttachedProperties.Converters
{
public class CellTooltipMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//here values are unset values.
//If I return a string, it is shown in the datagrid. But I can't do any logic because the values are null.
return "Hola";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
The attached property that I want to use to communicate the view with the style:
using System.Windows;
namespace TestStyleWithAttachedProperties.AttachedProperties
{
public static class CellWithErrorsAttachedProperty
{
//TextoTooltip01
//Primer texto del tooltip que se mostrarĂ¡.
public static readonly DependencyProperty TextoTooltip01Property =
DependencyProperty.RegisterAttached(
"TextoTooltip01",
typeof(string),
typeof(CellWithErrorsAttachedProperty));
public static string GetTextoTooltip01(DependencyObject obj)
{
return (string)obj.GetValue(TextoTooltip01Property);
}
public static void SetTextoTooltip01(DependencyObject obj, string value)
{
obj.SetValue(TextoTooltip01Property, value);
}
//TextoTooltip02
//Primer texto del tooltip que se mostrarĂ¡.
public static readonly DependencyProperty TextoTooltip02Property =
DependencyProperty.RegisterAttached(
"TextoTooltip02",
typeof(string),
typeof(CellWithErrorsAttachedProperty));
public static string GetTextoTooltip02(DependencyObject obj)
{
return (string)obj.GetValue(TextoTooltip01Property);
}
public static void SetTextoTooltip02(DependencyObject obj, string value)
{
obj.SetValue(TextoTooltip01Property, value);
}
//ES DATO CORRECTO
//Indica si el dato de la celda es correcto o no.
public static readonly DependencyProperty EsDatoCorrectoProperty =
DependencyProperty.RegisterAttached(
"EsDatoCorrecto",
typeof(bool),
typeof(CellWithErrorsAttachedProperty));
public static bool GetEsDatoCorrecto(DependencyObject obj)
{
return (bool)obj.GetValue(EsDatoCorrectoProperty);
}
public static void SetEsDatoCorrecto(DependencyObject obj, bool value)
{
obj.SetValue(EsDatoCorrectoProperty, value);
}
}
}
The class data that is source of the rows of the datagrid. I would like to bind the IsDataCorrect to the attached property.
The xml resource file with the styles:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ap="clr-namespace:TestStyleWithAttachedProperties.AttachedProperties"
xmlns:conv="clr-namespace:TestStyleWithAttachedProperties.Converters">
<conv:CellTooltipMultiValueConverter x:Key="CellTooltipMultiValueConverter"/>
<Style TargetType="{x:Type DataGridColumnHeader}" x:Key="DataGridColumnHeaderConTooltip">
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
<Setter Property="ToolTip" Value="{Binding Column.(ap:HeaderAttachedProperty.Tooltip), RelativeSource={RelativeSource Self}}"/>
</Style>
<Style TargetType="{x:Type DataGridCell}" x:Key="DataGridCellWithErrorsStyle">
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{StaticResource ResourceKey=CellTooltipMultiValueConverter}">
<MultiBinding.Bindings>
<Binding Path="(ap:CellWithErrorsAttachedProperty.TextoTooltip01)" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
<Binding Path="(ap:CellWithErrorsAttachedProperty.TextoTooltip02)" RelativeSource="{RelativeSource AncestorType=ToolTip}"/>
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(ap:CellWithErrorsAttachedProperty.EsDatoCorrecto), RelativeSource={RelativeSource AncestorType=DataGridCell}}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Orange"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
The attached property for show a tooltip in the header of the datagrid. Really this is not used for the problem that I have, because what I want to do it is show a tooltip in a cell, but in this way I show the complete code of the project.
using System.Windows;
namespace TestStyleWithAttachedProperties.AttachedProperties
{
public static class HeaderAttachedProperty
{
//TextoTooltip01
//Primer texto del tooltip que se mostrarĂ¡.
public static readonly DependencyProperty TooltipProperty =
DependencyProperty.RegisterAttached(
"Tooltip",
typeof(string),
typeof(HeaderAttachedProperty));
public static string GetTooltip(DependencyObject obj)
{
return (string)obj.GetValue(TooltipProperty);
}
public static void SetTooltip(DependencyObject obj, string value)
{
obj.SetValue(TooltipProperty, value);
}
}
}
I've searched for and found many questions similar mine but none of them seem to be quite my situation. Admittedly, mine is something of an edge-case. I'm hoping someone can spot what I'm missing here.
I've long been using a custom ItemsControl which derives from MultiSelector. I have a custom DataTemplate to draw the items in it. And they're drawn just fine if and only if I use the ItemTemplate property on the control.
But when I try instead to the ItemTemplateSelector property, my override of SelectTemplate is not being called. I've verified that it's creates and then set as the control's ItemTemplateSelector. But the breakpoint for its SelectTemplate override never gets hit.
The net effect is that the nice shapes that were previously being drawn beautifully by my one and only DataTemplate now just show up as string names.
The view I'm using is like this:
<UserControl x:Class="MyApp.Shapes.ShapeCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gcs="clr-namespace:MyApp.Shapes"
xmlns:gcp="clr-namespace:MyApp.Properties"
xmlns:net="http://schemas.mycompany.com/mobile/net"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyApp.Core;component/Resources/Styles/ShapeItemStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
<!--
Default data template for most ShapeVm types, custom data type for PolyLineVm
I've removed the contents for brevity but they draw Paths objects normally
-->
<DataTemplate x:Key="ShapeTemplate" DataType="{x:Type gcs:ShapeVm}"/>
<DataTemplate x:Key="PolylineTemplate" DataType="{x:Type gcs:PolyLineVm}"/>
<!-- ShapeTemplateSelector to pick the right one -->
<gcs:ShapeTemplateSelector x:Key="ShapeTemplateSelector"
DefaultTemplate="{StaticResource ShapeTemplate}"
PolyLineTemplate="{StaticResource PolylineTemplate}"/>
</ResourceDictionary>
</UserControl.Resources>
<Canvas x:Name="Scene">
<gcs:ShapesControl x:Name="ShapesControl"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemContainerStyle="{StaticResource ShapeItemStyle}"
ItemsSource="{Binding Shapes}"
ItemTemplateSelector="{StaticResource ShapeTemplateSelector}"
>
<!-- If I use this line instead of ItemContainerStyle, It *does* pick up shape template -->
<!-- ItemTemplate="{StaticResource ShapeTemplate}" -->
<gcs:ShapesControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" IsItemsHost="True" />
</ItemsPanelTemplate>
</gcs:ShapesControl.ItemsPanel>
</gcs:ShapesControl>
</Canvas>
</UserControl>
Custom DataTemplateSelector
public class ShapeTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
*** THIS NEVER EVEN GETS CALLED ***
return item is PolyLineVm ? PolyLineTemplate : DefaultTemplate;
}
public DataTemplate PolyLineTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
}
Custom MultiSelector ("ShapesControl")
using System.Collections.Specialized;
using System.Windows.Controls;
namespace MyApp.Shapes
{
// Created By:
// Date: 2017-08-25
using System.Linq;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
/// <summary>
/// ShapesControl - our own version of a WPF MultiSelector. Basically an
/// ItemsControl that can select multiple items at once. We need this to
/// handle user manipulation of shapes on the ShapeCanvas and, potentially,
/// elsewhere.
/// </summary>
public class ShapesControl : MultiSelector
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is ShapeItem);
}
protected override DependencyObject GetContainerForItemOverride()
{
// Each item we display is wrapped in our own container: ShapeItem
// This override is how we enable that.
return new ShapeItem();
}
// ...Other handlers are multi-selection overrides and removed for clarity
}
}
Finally, the ShapeItemStyle I use to draw my custom ShapeItems
<Style x:Key="ShapeItemStyle"
TargetType="{x:Type gcs:ShapeItem}"
d:DataContext="{d:DesignInstance {x:Type gcs:ShapeVm}}"
>
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type gcs:ShapeItem}">
<Grid>
>
<!-- ContentPresenter for the ShapeVm that this ShapeItem contains -->
<ContentPresenter x:Name="PART_Shape"
Content="{TemplateBinding ContentControl.Content}"
ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
IsHitTestVisible="False"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"
RenderTransformOrigin="{TemplateBinding ContentControl.RenderTransformOrigin}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
(Edit to add ShapeItem per request. Note that this includes selection code that interacts with the custom MultiSelector ("ShapesControl") above. I removed some of those functions from ShapesControl code for brevity as they're triggered by mouse-clicks and I couldn't see how they could possibly prevent a custom DataTemplateSelector from being invoked. But I've posted all of ShapeItem here)
namespace MyApp.Shapes
{
[TemplatePart(Name="PART_Shape", Type=typeof(ContentPresenter))]
public class ShapeItem : ContentControl
{
public ShapeVm ShapeVm => DataContext as ShapeVm;
public ShapeType ShapeType => ShapeVm?.ShapeType ?? ShapeType.None;
static ShapeItem()
{
DefaultStyleKeyProperty.OverrideMetadata
(typeof(ShapeItem),
new FrameworkPropertyMetadata(typeof(ShapeItem)));
}
private bool WasSelectedWhenManipulationStarted { get; set; }
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
ParentSelector?.NotifyItemClicked(this, true);
e.Handled = true;
}
// The following ShapeItem manipulation handlers only handle the case of
// moving the shape as a whole across the canvas. These handlers do NOT
// handle the case of resizing the shape. Those handlers are on the
// ShapeHandleThumb class.
protected override void OnManipulationStarting(ManipulationStartingEventArgs e)
{
// The Canvas defines the coordinates for manipulation
e.ManipulationContainer = this.FindVisualParent<Canvas>();
base.OnManipulationStarting(e);
e.Handled = true;
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
Debug.Assert(e.ManipulationContainer is Canvas);
base.OnManipulationStarted(e);
// If this item was selected already, this manipulation might be a
// move. In that case, wait until we're done with the manipulation
// before deciding whether to notify the control.
if (IsSelected)
WasSelectedWhenManipulationStarted = true;
else
ParentSelector.NotifyItemClicked(this, false);
e.Handled = true;
}
protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
{
Debug.Assert(e.ManipulationContainer is Canvas);
base.OnManipulationDelta(e);
ParentSelector.NotifyItemMoved(e.DeltaManipulation.Translation);
e.Handled = true;
}
protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
{
Debug.Assert(e.ManipulationContainer is Canvas);
base.OnManipulationCompleted(e);
if (WasSelectedWhenManipulationStarted)
{
// If nothing moved appreciably, unselect everything. This is how I
// am detecting just a Tap. I have to think there is a better way...
var t = e.TotalManipulation.Translation;
if (Math.Abs(t.X) < 0.0001 && Math.Abs(t.Y) < 0.0001)
ParentSelector.NotifyItemClicked(this, false);
}
e.Handled = true;
}
private bool IsControlKeyPressed =>
(Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
internal ShapesControl ParentSelector =>
ItemsControl.ItemsControlFromItemContainer(this) as ShapesControl;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Debug.Assert(ReferenceEquals(
ParentSelector.ItemContainerGenerator.ItemFromContainer(this),
ShapeVm));
}
public static readonly DependencyProperty IsSelectedProperty =
Selector.IsSelectedProperty.AddOwner(
typeof(ShapeItem),
new FrameworkPropertyMetadata(false, OnIsSelectedChanged));
public bool IsSelected
{
get
{
var value = GetValue(IsSelectedProperty);
return value != null && (bool)value;
}
set { SetValue(IsSelectedProperty, value); }
}
private static void OnIsSelectedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
if (!(target is ShapeItem item))
return;
var evt = (bool)e.NewValue ? Selector.SelectedEvent : Selector.UnselectedEvent;
item.RaiseEvent(new RoutedEventArgs(evt, item));
}
}
}
The problem is with following code:
protected override DependencyObject GetContainerForItemOverride()
{
// Each item we display is wrapped in our own container: ShapeItem
// This override is how we enable that.
return new ShapeItem();
}
When you override GetContainerForItemOverride method, it is your code's responsibility to use ItemTemplateSelector and attach it to ItemsControl.
I have two kinds of menu items according to the login. So, by using the property in the ViewModel Class
bool IsAdmin {get; set;}
I have to change the menu item content.I am not familiar with data template. I want to define all the menu items in the xaml itself (might be using the data templates).
How we can bind the differnt menu items by using the data trigger.
Can anyone can give a smaller example for this. using only this property and no c# codes.
Use ContentControl and Styles for max flexability in changing the view betwin Admin or not Admin views
<UserControl.Resources>
<!--*********** Control templates ***********-->
<ControlTemplate x:Key="ViewA">
<Views:AView/>
</ControlTemplate>
<ControlTemplate x:Key="ViewB">
<Views:BView />
</ControlTemplate>
</UserControl.Resources>
<Grid>
<ContentControl DataContext="{Binding}" Grid.Row="1">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource ViewA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsAdmin}" Value="True">
<Setter Property="Template" Value="{StaticResource ViewB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl >
</Grid>
Please keep in mind that you will have to implement the INPC interface on your VM in order to be able to change the state (is admin or not) on the fly.If not the change will be accepted only once(on the creation of the class that is holding the IsAdmin property). Here is the INPC implementation example:
public class UserControlDataContext:BaseObservableObject
{
private bool _isAdmin;
public bool IsAdmin
{
get { return _isAdmin; }
set
{
_isAdmin = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
I have problems with binding a treeview to a datagrid's selected item.
they are in different views, but datagrid's selected item is already passed to treeview's related viewmodel.
There is a SelectedGroup property in treeview's related viewmodel which is datagrid's selected item and its type is Group. I want to bind the ID field of Group to treeview, i.e. I want the ID of selected item to be selected in treeview and also be updated by selected value of treeview.
I couldn't find out how to bind.
Here's my treeview's skeleton, which can just lists all of the groups hierarchically.
Can anyone help me on filling the required fields please?
Thanks in advance.
<TreeView Grid.Column="1" Grid.Row="4" Height="251" HorizontalAlignment="Left"
Margin="4,3,0,0" Name="parentGroupTreeView" VerticalAlignment="Top"
Width="246" ItemsSource="{Binding Groups}" ItemContainerStyle="{x:Null}"
SelectedValuePath="ID">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding
Converter={x:Static Member=conv:GroupSubGroupsConv.Default}}">
<Label Name="groupLabel" Content="{Binding GroupName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Start by taking a look at the following article by Josh Smith on Simplifying the WPF TreeView by Using the ViewModel Pattern.
I am also using the DataGrid from the WPF toolkit.
To get a sense as to how this code works look at the IsSelected property below.
Here is the XAML that contains a tree and a datagrid:
<Window x:Class="TreeviewDatagrid.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
xmlns:ViewModels="clr-namespace:TreeviewDatagrid.ViewModels" Title="Main Window" Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding Groups}"
Grid.Column="0">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type ViewModels:GroupViewModel}"
ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<WpfToolkit:DataGrid
Grid.Column="1"
SelectedItem="{Binding Path=SelectedGroup, Mode=TwoWay}"
ItemsSource="{Binding Path=Groups, Mode=OneWay}" >
</WpfToolkit:DataGrid>
</Grid>
</DockPanel>
</Window>
Here is the main view model that the TreeView and DataGrid bind to:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using TreeviewDatagrid.Models;
namespace TreeviewDatagrid.ViewModels
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Group g1 = new Group();
g1.Id = 1;
g1.GroupName = "Planners";
g1.Description = "People who plan";
GroupViewModel gvm1 = new GroupViewModel(this, g1);
Group g2 = new Group();
g2.Id = 2;
g2.GroupName = "Thinkers";
g2.Description = "People who think";
GroupViewModel gvm2 = new GroupViewModel(this, g2);
Group g3 = new Group();
g3.Id = 3;
g3.GroupName = "Doers";
g3.Description = "People who do";
GroupViewModel gvm3 = new GroupViewModel(this, g3);
IList<GroupViewModel> list = new List<GroupViewModel>();
list.Add(gvm1);
list.Add(gvm2);
list.Add(gvm3);
_selectedGroup = gvm1;
_groups = new ReadOnlyCollection<GroupViewModel>(list);
}
readonly ReadOnlyCollection<GroupViewModel> _groups;
public ReadOnlyCollection<GroupViewModel> Groups
{
get { return _groups; }
}
private GroupViewModel _selectedGroup;
public GroupViewModel SelectedGroup
{
get
{
return _selectedGroup;
}
set
{
// keep selection in grid in-sync with tree
_selectedGroup.IsSelected = false;
_selectedGroup = value;
_selectedGroup.IsSelected = true;
OnPropertyChanged("SelectedGroup");
}
}
public void ChangeSelectedGroup(GroupViewModel selectedGroup)
{
_selectedGroup = selectedGroup;
OnPropertyChanged("SelectedGroup");
}
}
}
Here is the viewmodel that I use to bind to the grid and the tree:
using TreeviewDatagrid.Models;
namespace TreeviewDatagrid.ViewModels
{
public class GroupViewModel : TreeViewItemViewModel
{
private readonly MainViewModel _mainViewModel;
readonly Group _group;
bool _isSelected;
public GroupViewModel(MainViewModel mainViewModel, Group group) : base(null, true)
{
_mainViewModel = mainViewModel;
_group = group;
}
public string GroupName
{
get { return _group.GroupName; }
}
public override bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
if (_isSelected )
{
// keep tree selection in sync with grid
_mainViewModel.ChangeSelectedGroup(this);
}
this.OnPropertyChanged("IsSelected");
}
}
}
protected override void LoadChildren()
{
// load children in treeview here
}
}
}
For completeness here is the Group object:
namespace TreeviewDatagrid.Models
{
public class Group
{
public int Id { get; set; }
public string GroupName { get; set; }
public string Description { get; set; }
}
}
And also the base class for the TreeView:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace TreeviewDatagrid.ViewModels
{
/// <summary>
/// Base class for all ViewModel classes displayed by TreeViewItems.
/// This acts as an adapter between a raw data object and a TreeViewItem.
/// </summary>
public class TreeViewItemViewModel : INotifyPropertyChanged
{
#region Data
static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
readonly ObservableCollection<TreeViewItemViewModel> _children;
readonly TreeViewItemViewModel _parent;
bool _isExpanded;
bool _isSelected;
#endregion // Data
#region Constructors
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
// This is used to create the DummyChild instance.
private TreeViewItemViewModel()
{
}
#endregion // Constructors
#region Presentation Members
#region Children
/// <summary>
/// Returns the logical child items of this object.
/// </summary>
public ObservableCollection<TreeViewItemViewModel> Children
{
get { return _children; }
}
#endregion // Children
#region HasLoadedChildren
/// <summary>
/// Returns true if this object's Children have not yet been populated.
/// </summary>
public bool HasDummyChild
{
get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}
#endregion // HasLoadedChildren
#region IsExpanded
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
// Lazy load the child items, if necessary.
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
#endregion // IsExpanded
#region IsSelected
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public virtual bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
#endregion // IsSelected
#region LoadChildren
/// <summary>
/// Invoked when the child items need to be loaded on demand.
/// Subclasses can override this to populate the Children collection.
/// </summary>
protected virtual void LoadChildren()
{
}
#endregion // LoadChildren
#region Parent
public TreeViewItemViewModel Parent
{
get { return _parent; }
}
#endregion // Parent
#endregion // Presentation Members
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
}
}
Does anyone know of WPF code examples using Prism in which modules each register themselves as a menuitem in a menu within another module?
(I've currently got an application which tries to do this with the EventAggregator, so one module listens for published events from other modules which need to have their title in the menu as a menu item, but I'm getting problems with the order of loading and threading etc. I want to find an example that uses classic Prism structure to do this.)
I'm thinking in terms of this:
Shell.xaml:
<DockPanel>
<TextBlock Text="Menu:" DockPanel.Dock="Top"/>
<Menu
Name="MenuRegion"
cal:RegionManager.RegionName="MenuRegion"
DockPanel.Dock="Top"/>
</DockPanel>
Contracts View:
<UserControl x:Class="ContractModule.Views.AllContracts"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MenuItem Header="Contracts">
</MenuItem>
</UserControl>
Customers View:
<UserControl x:Class="CustomerModule.Views.CustomerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<MenuItem Header="Customers">
</MenuItem>
</UserControl>
But up to know I've done non-Prism MVVM application structure and Menus were always nicely bound to ObservableCollections in the ViewModel and the above seems to break this nice pattern. Is the above the customary way to do it in Prism?
Update:
I created a sample for you. It's here: Sample (now dead link)
It's got a few things you have probably not thought of yet, like a contract that will allow your modules to control your shell (so you can do stuff like Open Window, that kind of thing). It is designed with MVVM in mind. I don't know if you are using that, but I would consider it.
I tried for a few minutes to get the tab titles correct, but I ended up leaving off with "A Tab". It's left as an exercise for you if you go with a tabbed UI. I've designed it to be lookless, so you can replace the XAML in the Shell.xaml without breaking anything. That's one of the advantages to the RegionManager stuff if you use it right.
Anyway, good luck!
I've never seen an example of this, but you'd have to implement this yourself.
You'd have to create your own interface, something like this:
public interface IMenuRegistry
{
void RegisterViewWithMenu(string MenuItemTitle, System.Type viewType);
}
Your Modules then would declare a dependency on an IMenuRegistry and register their views.
Your implementation of IMenuRegistry (which you would likely implement and register in the same project that hosts your Bootstrapper) you would add those menu items to your menu or treeview or whatever you are using for your menu.
When a user clicks on an item you will have to use your Bootstrapper.Container.Resolve(viewType) method to create an instance of the view and stuff it in whatever placeholder you want to show it in.
I am using MEF along with prism 6.0 and MVVM
1.Create a Menuviewmodel class for Leafmenu and TopLevel MenuViewmodel class for Toplevel menu. Menuviewmodel class will have all the properties you want to bind your menu with. Moduleui implementing this interafce must have an attribute like this
[Export(typeof(IMenu))]
public class MenuViewModel:ViewModelBase
{
public String Name { get; private set; }
public UIMenuOptions ParentMenu { get; private set; }
private bool _IsToolTipEnabled;
public bool IsToolTipEnabled
{
get
{
return _IsToolTipEnabled;
}
set
{
SetField(ref _IsToolTipEnabled, value);
}
}
private String _ToolTipMessage;
public String ToolTipMessage
{
get
{
return _ToolTipMessage;
}
set
{
SetField(ref _ToolTipMessage, value);
}
}
private IExtensionView extensionView;
public MenuViewModel(String name, UIMenuOptions parentmenu,
bool isMenuCheckable = false,
IExtensionView extensionView =null)
{
if(name.Contains('_'))
{
name= name.Replace('_', ' ');
}
name = "_" + name;
this.Name = name;
this.ParentMenu = parentmenu;
this.IsMenuCheckable = isMenuCheckable;
this.extensionView = extensionView ;
}
private RelayCommand<object> _OpenMenuCommand;
public ObservableCollection<MenuViewModel> MenuItems { get; set; }
public ICommand OpenMenuCommand
{
get
{
if(_OpenMenuCommand==null)
{
_OpenMenuCommand = new RelayCommand<object>((args =>
OpenMenu(null)));
}
return _OpenMenuCommand;
}
}
private void OpenMenu(object p)
{
if (extensionView != null)
{
extensionView .Show();
}
}
private bool _IsMenuEnabled=true;
public bool IsMenuEnabled
{
get
{
return _IsMenuEnabled;
}
set
{
SetField(ref _IsMenuEnabled, value);
}
}
public bool IsMenuCheckable
{
get;
private set;
}
private bool _IsMenuChecked;
public bool IsMenuChecked
{
get
{
return _IsMenuChecked;
}
set
{
SetField(ref _IsMenuChecked, value);
}
}
}
public class ToplevelMenuViewModel:ViewModelBase
{
public ObservableCollection<MenuViewModel> ChildMenuViewModels {
get; private set; }
public String Header { get; private set; }
public ToplevelMenuViewModel(String header,
IEnumerable<MenuViewModel> childs)
{
this.Header ="_"+ header;
this.ChildMenuViewModels =new
ObservableCollection<MenuViewModel>(childs);
}
}
}
Create an IMenu Interface wich has MenuViewModel property
public interface IMenu
{
MenuViewModel ExtensionMenuViewModel
{
get;
}
}
3.You need to implement IMenu Interface in ModuleUi of all your modules which will get loaded into a menu.
4.Implement MefBootstrapper
5.Override Configure aggregate catalog method
6.To the catalog add diretory catalog containing all your module dlls, IMenu interface dll.Code is below
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new
AssemblyCatalog(typeof(Bootstrapper).Assembly));
AggregateCatalog.Catalogs.Add(new
AssemblyCatalog(typeof(IMenu).Assembly));
//create a directorycatalog with path of a directory conatining
//your module dlls
DirectoryCatalog dc = new DirectoryCatalog(#".\Extensions");
AggregateCatalog.Catalogs.Add(dc);
}
in your main project add refence to IMenu interafce dll
8.In mainwindow.xaml.cs class declare a property
public ObservableCollection ClientMenuViewModels
{ get; private set; }
declare a private field
private IEnumerable<IMenu> menuExtensions;
In your mainwindow or shell constructor
[ImportingConstructor]
public MainWindow([ImportMany] IEnumerable<IMenu> menuExtensions)
{
this.menuExtensions = menuExtensions;
this.DataContext=this;
}
private void InitalizeMenuAndOwners()
{
if (ClientMenuViewModels == null)
{
ClientMenuViewModels = new
ObservableCollection<ToplevelMenuViewModel>();
}
else
{
ClientMenuViewModels.Clear();
}
if (menuExtensions != null)
{
var groupings = menuExtensions.Select
(mnuext => mnuext.ClientMenuViewModel).GroupBy(mvvm =>
mvvm.ParentMenu);
foreach (IGrouping<UIMenuOptions, MenuViewModel> grouping in
groupings)
{
UIMenuOptions parentMenuName = grouping.Key;
ToplevelMenuViewModel parentMenuVM = new
ToplevelMenuViewModel(
parentMenuName.ToString(),
grouping.Select(grp => { return (MenuViewModel)grp; }));
ClientMenuViewModels.Add(parentMenuVM);
}
}}
}
In your Shell.xaml or Mainwindow.xaml define a menu region and bind the itemssource property to ClientMenuViewModels
<Menu HorizontalAlignment="Left"
Background="#FF0096D6"
Foreground="{StaticResource menuItemForegroundBrush}"
ItemsSource="{Binding ClientMenuViewModels}"
TabIndex="3">
<Menu.Resources>
<Style x:Key="subMneuStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="#FF0096D6" />
<Setter Property="FontFamily" Value="HP Simplified" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Background" Value="White" />
<Setter Property="Command" Value="{Binding
OpenMenuCommand}" />
<Setter Property="IsCheckable" Value="{Binding
IsMenuCheckable}" />
<Setter Property="IsChecked" Value="{Binding
IsMenuChecked, Mode=TwoWay}" />
<Setter Property="IsEnabled" Value="{Binding
IsMenuEnabled, Mode=TwoWay}" />
<Setter Property="ToolTip" Value="{Binding
ToolTipMessage, Mode=OneWay}" />
<Setter Property="ToolTipService.ShowOnDisabled" Value="
{Binding IsToolTipEnabled, Mode=OneWay}" />
<Setter Property="ToolTipService.IsEnabled" Value="
{Binding IsToolTipEnabled, Mode=OneWay}" />
<Setter Property="ToolTipService.ShowDuration"
Value="3000" />
<Setter Property="ToolTipService.InitialShowDelay"
Value="10" />
</Style>
<my:MyStyleSelector x:Key="styleSelector" ChildMenuStyle="
{StaticResource subMneuStyle}" />
<HierarchicalDataTemplate DataType="{x:Type
plugins:ToplevelMenuViewModel}"
ItemContainerStyleSelector="{StaticResource styleSelector}"
ItemsSource="{Binding ChildMenuViewModels}">
<Label Margin="0,-5,0,0"
Content="{Binding Header}"
FontFamily="HP Simplified"
FontSize="12"
Foreground="{StaticResource menuItemForegroundBrush}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type plugins:MenuViewModel}">
<Label VerticalContentAlignment="Center"
Content="{Binding Name}"
Foreground="#FF0096D6" />
</DataTemplate>
</Menu.Resources>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
</Menu>
public class MyStyleSelector : StyleSelector
{
public Style ChildMenuStyle { get; set; }
public Style TopLevelMenuItemStyle { get; set; }
public override Style SelectStyle(object item, DependencyObject
container)
{
if (item is MenuViewModel)
{
return ChildMenuStyle;
}
//if(item is ToplevelMenuViewModel)
//{
// return TopLevelMenuItemStyle;
//}
return null;
}
}
here is ViewModelBase class
public class ViewModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler =Volatile.Read(ref PropertyChanged);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
};
}
protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName="")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
RelayCommand class is below
public class RelayCommand<T> : ICommand
{
#region Fields
private readonly Action<T> _execute = null;
private readonly Predicate<T> _canExecute = null;
#endregion
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command with conditional execution.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
#endregion
}