I am in the middle of creating WPF utility library but I'm stuck on how to create an easy to use enumeration converter which will help in binding any enumeration to a ComboBox control and when enumeration is declared with [Flags] attribute, ComboBox will contain checkboxes data bound to it. Solution needs to be generic so that it will be easy to use for any enumeration and I suppose it would need fresh pair of eyes of an experienced WPF developer.
So far I've come up with a combination of ObjectDataProvider as a source of enum items and 2 converters, one for ItemsSource and another for SelectedValue.
I used CollectionView as a base class for ItemsSource to use CurrentChanged but it does not work as I tried to raise OnCurrentChanged after the collection has been notified about any CheckBox being checked without converter's ConvertBack being executed.
The problem is that data bound property to which ComboBox SelectedValue property is bound, is not updated unless I'll change the selected item (not tick the checkbox).
I've set IsSynchronizedWithCurrentItem but it did not help.
How to force updating of SelectedValue from data bound object? I have a freedom of choice so the solution might involve custom attached properties, inheriting from ComboBox etc. Being really new to WPF I think although the solution is almost right as it almost works, there can be a simpler way of getting to it.
Another issue would be how to customize text displayed on ComboBox so it will contain aggregated selections of ticked checkboxes.
I uploaded VS2012 project here. The structure of a project resembles real application with resources held separately (for simplicity in App.xaml).
Sorry I have not included any code but there is a lot of code involved.
Below is the XAML declaration for a CheckBox bound to standard enumeration (although enumeration used is with [Flags]), which works fine and further a CheckBox bound to [Flags] enumeration.
<ComboBox ItemsSource="{Binding Source={StaticResource someItemsSource}}"
SelectedValue="{Binding BindToSomeItems}"
Style="{StaticResource enumComboBox}"/>
<ComboBox ItemsSource="{Binding BindToSomeItems, Converter={local:LocalizedFlagsEnumConverter}}"
IsSynchronizedWithCurrentItem="True"
SelectedValue="{Binding BindToSomeItems, Mode=TwoWay, Converter={local:SelectedFlagsEnumConverter}}"
Style="{StaticResource flagsComboBox}"
/>
And resources (which explain usage of value converters):
<Application.Resources>
<Style x:Key="enumComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Path=.,Mode=OneWay, Converter={local:LocalizedEnumConverter}}" Style="{x:Null}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="flagsComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Checked,Mode=TwoWay}"/>
<TextBlock Text="{Binding Path=Name,Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
I use a custom Checkbox to do so. It works perfectly and simply. See comments on top of the class to know how to use it.
Hope it helps...
using System;
using System.Windows;
using System.Windows.Controls;
namespace HQ.Wpf.Util.MyControl
{
/// <summary>
/// Usage: Bind EnumFlag and Two way binding on EnumValue instead of IsChecked
/// Example: <myControl:CheckBoxForEnumWithFlagAttribute
/// EnumValue="{Binding SimulationNatureTypeToCreateStatsCacheAtEndOfSimulation, Mode=TwoWay}"
/// EnumFlag="{x:Static Core:SimulationNatureType.LoadFlow }">Load Flow results</myControl:CheckBoxForEnumWithFlagAttribute>
/// </summary>
public class CheckBoxForEnumWithFlagAttribute : CheckBox
{
// ************************************************************************
public static DependencyProperty EnumValueProperty =
DependencyProperty.Register("EnumValue", typeof(object), typeof(CheckBoxForEnumWithFlagAttribute), new PropertyMetadata(EnumValueChangedCallback));
public static DependencyProperty EnumFlagProperty =
DependencyProperty.Register("EnumFlag", typeof(object), typeof(CheckBoxForEnumWithFlagAttribute), new PropertyMetadata(EnumFlagChangedCallback));
// ************************************************************************
public CheckBoxForEnumWithFlagAttribute()
{
base.Checked += CheckBoxForEnumWithFlag_Checked;
base.Unchecked += CheckBoxForEnumWithFlag_Unchecked;
}
// ************************************************************************
private static void EnumValueChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var checkBoxForEnumWithFlag = dependencyObject as CheckBoxForEnumWithFlagAttribute;
if (checkBoxForEnumWithFlag != null)
{
checkBoxForEnumWithFlag.RefreshCheckBoxState();
}
}
// ************************************************************************
private static void EnumFlagChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var checkBoxForEnumWithFlag = dependencyObject as CheckBoxForEnumWithFlagAttribute;
if (checkBoxForEnumWithFlag != null)
{
checkBoxForEnumWithFlag.RefreshCheckBoxState();
}
}
// ************************************************************************
public object EnumValue
{
get { return GetValue(EnumValueProperty); }
set { SetValue(EnumValueProperty, value); }
}
// ************************************************************************
public object EnumFlag
{
get { return GetValue(EnumFlagProperty); }
set { SetValue(EnumFlagProperty, value); }
}
// ************************************************************************
private void RefreshCheckBoxState()
{
if (EnumValue != null)
{
if (EnumValue is Enum)
{
Type underlyingType = Enum.GetUnderlyingType(EnumValue.GetType());
dynamic valueAsInt = Convert.ChangeType(EnumValue, underlyingType);
dynamic flagAsInt = Convert.ChangeType(EnumFlag, underlyingType);
base.IsChecked = ((valueAsInt & flagAsInt) > 0);
}
}
}
// ************************************************************************
private void CheckBoxForEnumWithFlag_Checked(object sender, RoutedEventArgs e)
{
RefreshEnumValue();
}
// ************************************************************************
void CheckBoxForEnumWithFlag_Unchecked(object sender, RoutedEventArgs e)
{
RefreshEnumValue();
}
// ************************************************************************
private void RefreshEnumValue()
{
if (EnumValue != null)
{
if (EnumValue is Enum)
{
Type underlyingType = Enum.GetUnderlyingType(EnumValue.GetType());
dynamic valueAsInt = Convert.ChangeType(EnumValue, underlyingType);
dynamic flagAsInt = Convert.ChangeType(EnumFlag, underlyingType);
dynamic newValueAsInt = valueAsInt;
if (base.IsChecked == true)
{
newValueAsInt = valueAsInt | flagAsInt;
}
else
{
newValueAsInt = valueAsInt & ~flagAsInt;
}
if (newValueAsInt != valueAsInt)
{
object o = Enum.ToObject(EnumValue.GetType(), newValueAsInt);
EnumValue = o;
}
}
}
}
// ************************************************************************
}
}
Related
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 a little problem here. I've created custom TreeView using RadTreeView. It all works nice, but I've encountered an obstacle. I've set DependencyProperty for SelectedItem in TreeView. I nest my control in View, bind property to SelectedItem in TwoWay mode, but bound property won't update, it's null all the time, despite DependencyProperty value being set.
Here's tree xaml:
<Grid xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:sdk='http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk'
xmlns:telerik='http://schemas.telerik.com/2008/xaml/presentation' x:Name='this' >
<Grid.Resources>
<DataTemplate x:Key='ChildTemplate'>
<TextBlock Text='{Binding Path=ChildPath}' Margin='5,0' />
</DataTemplate>
<telerik:HierarchicalDataTemplate x:Key='NameTemplate' ItemsSource='{Binding ChildrenCollectionPath}' ItemTemplate='{StaticResource ChildTemplate}'>
<TextBlock Text='{Binding Path=ParentPath }' Padding='7'/>
</telerik:HierarchicalDataTemplate>
</Grid.Resources>
<telerik:RadTreeView x:Name='rtvTreeView' Padding='5' BorderThickness='0' IsEditable='False' IsLineEnabled='True' IsExpandOnDblClickEnabled='False' ItemTemplate='{StaticResource NameTemplate}' />
</Grid>
Below is way I nest the control in View:
<windows:TreeViewReuse CollectionSource="{Binding SitesCollectionWithAddress}" ParentPath="Napis" Grid.Column="0" BorderThickness="2" SelectedItemD="{Binding SelectedSide, ElementName=this, UpdateSourceTrigger=Explicit, Mode=TwoWay}" ChildPath="FullAddress" ChildrenCollectionPath="AdresyStrony" BorderBrush="Red" DoubleClickCommand="{Binding TreeViewDoubleClick}">
</windows:TreeViewReuse>
And here's Tree's code behind in parts:
public partial class TreeViewReuse : UserControl
{
static Telerik.Windows.FrameworkPropertyMetadata propertyMetaData = new Telerik.Windows.FrameworkPropertyMetadata(null,
Telerik.Windows.FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(SelectedItemChangedCallback));
public object SelectedItemD
{
get { return GetValue(SelectedItemDProperty); }
set { SetValue(SelectedItemDProperty, value); }
}
public static readonly DependencyProperty SelectedItemDProperty =
DependencyProperty.Register("SelectedItemD", typeof(object), typeof(TreeViewReuse), propertyMetaData);
public TreeViewReuse()
{
InitializeComponent();
Loaded += new RoutedEventHandler(TreeViewReuse_Loaded);
}
void treeView_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
SelectedItemD = _treeView.SelectedItem;
}
static private void SelectedItemChangedCallback(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
}
Does anyone have an idea why property bound to SelectedItemD does not update? I don't care about setting tree's selected item from it, I only want to set it to selected item.
Here's property:
public StronaSprawy SelectedSide
{
get
{
return _selectedSide;
}
set
{
_selectedSide = value;
}
}
Your Dependency Property looks fine.. all except for that Telerik.Windows.FrameworkPropertyMetadata instance.
Silverlight does not support setting meta data options, so I cant think how the Telerik implementation will achieve that. It is possible that Telerik have their own DP implementation, or even that this type of property meta data only works with their controls.
Try using the standard System.Windows.PropertyMetaData type instead and see if that works for you.
I'm using a WPF ListView control which displays a list of databound items.
<ListView ItemsSource={Binding MyItems}>
<ListView.View>
<GridView>
<!-- declare a GridViewColumn for each property -->
</GridView>
</ListView.View>
</ListView>
I'm trying to obtain a behavior similar to the ListView.SelectionChanged event, only I want to also detect if the currently selected item is clicked. The SelectionChanged event does not fire if the same item is clicked again (obviously).
What would be the best (cleanest) way to approach this?
Use the ListView.ItemContainerStyle property to give your ListViewItems an EventSetter that will handle the PreviewMouseLeftButtonDown event. Then, in the handler, check to see if the item that was clicked is selected.
XAML:
<ListView ItemsSource={Binding MyItems}>
<ListView.View>
<GridView>
<!-- declare a GridViewColumn for each property -->
</GridView>
</ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListViewItem_PreviewMouseLeftButtonDown" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
Code-behind:
private void ListViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var item = sender as ListViewItem;
if (item != null && item.IsSelected)
{
//Do your stuff
}
}
You can handle the ListView's PreviewMouseLeftButtonUp event.
The reason not to handle the PreviewMouseLeftButtonDown event is that, by the time when you handle the event, the ListView's SelectedItem may still be null.
XAML:
<ListView ... PreviewMouseLeftButtonUp="listView_Click"> ...
Code behind:
private void listView_Click(object sender, RoutedEventArgs e)
{
var item = (sender as ListView).SelectedItem;
if (item != null)
{
...
}
}
These are all great suggestions, but if I were you, I would do this in your view model. Within your view model, you can create a relay command that you can then bind to the click event in your item template. To determine if the same item was selected, you can store a reference to your selected item in your view model. I like to use MVVM Light to handle the binding. This makes your project much easier to modify in the future, and allows you to set the binding in Blend.
When all is said and done, your XAML will look like what Sergey suggested. I would avoid using the code behind in your view. I'm going to avoid writing code in this answer, because there is a ton of examples out there.
Here is one:
How to use RelayCommand with the MVVM Light framework
If you require an example, please comment, and I will add one.
~Cheers
I said I wasn't going to do an example, but I am. Here you go.
In your project, add MVVM Light Libraries Only.
Create a class for your view. Generally speaking, you have a view model for each view (view: MainWindow.xaml && viewModel: MainWindowViewModel.cs)
Here is the code for the very, very, very basic view model:
All included namespace (if they show up here, I am assuming you already added the reference to them. MVVM Light is in Nuget)
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
Now add a basic public class:
/// <summary>
/// Very basic model for example
/// </summary>
public class BasicModel
{
public string Id { get; set; }
public string Text { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="text"></param>
public BasicModel(string text)
{
this.Id = Guid.NewGuid().ToString();
this.Text = text;
}
}
Now create your viewmodel:
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
ModelsCollection = new ObservableCollection<BasicModel>(new List<BasicModel>() {
new BasicModel("Model one")
, new BasicModel("Model two")
, new BasicModel("Model three")
});
}
private BasicModel _selectedBasicModel;
/// <summary>
/// Stores the selected mode.
/// </summary>
/// <remarks>This is just an example, may be different.</remarks>
public BasicModel SelectedBasicModel
{
get { return _selectedBasicModel; }
set { Set(() => SelectedBasicModel, ref _selectedBasicModel, value); }
}
private ObservableCollection<BasicModel> _modelsCollection;
/// <summary>
/// List to bind to
/// </summary>
public ObservableCollection<BasicModel> ModelsCollection
{
get { return _modelsCollection; }
set { Set(() => ModelsCollection, ref _modelsCollection, value); }
}
}
In your viewmodel, add a relaycommand. Please note, I made this async and had it pass a parameter.
private RelayCommand<string> _selectItemRelayCommand;
/// <summary>
/// Relay command associated with the selection of an item in the observablecollection
/// </summary>
public RelayCommand<string> SelectItemRelayCommand
{
get
{
if (_selectItemRelayCommand == null)
{
_selectItemRelayCommand = new RelayCommand<string>(async (id) =>
{
await selectItem(id);
});
}
return _selectItemRelayCommand;
}
set { _selectItemRelayCommand = value; }
}
/// <summary>
/// I went with async in case you sub is a long task, and you don't want to lock you UI
/// </summary>
/// <returns></returns>
private async Task<int> selectItem(string id)
{
this.SelectedBasicModel = ModelsCollection.FirstOrDefault(x => x.Id == id);
Console.WriteLine(String.Concat("You just clicked:", SelectedBasicModel.Text));
//Do async work
return await Task.FromResult(1);
}
In the code behind for you view, create a property for you viewmodel and set the datacontext for your view to the viewmodel (please note, there are other ways to do this, but I am trying to make this a simple example.)
public partial class MainWindow : Window
{
public MainWindowViewModel MyViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
MyViewModel = new MainWindowViewModel();
this.DataContext = MyViewModel;
}
}
In your XAML, you need to add some namespaces to the top of your code
<Window x:Class="Basic_Binding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:Custom="clr-namespace:GalaSoft.MvvmLight;assembly=GalaSoft.MvvmLight"
Title="MainWindow" Height="350" Width="525">
I added "i" and "Custom."
Here is the ListView:
<ListView
Grid.Row="0"
Grid.Column="0"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding ModelsCollection}"
ItemTemplate="{DynamicResource BasicModelDataTemplate}">
</ListView>
Here is the ItemTemplate for the ListView:
<DataTemplate x:Key="BasicModelDataTemplate">
<Grid>
<TextBlock Text="{Binding Text}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectItemRelayCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding Id}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</Grid>
</DataTemplate>
Run your application, and check out the output window. You can use a converter to handle the styling of the selected item.
This may seem really complicated, but it makes life a lot easier down the road when you need to separate your view from your ViewModel (e.g. develop a ViewModel for multiple platforms.) Additionally, it makes working in Blend 10x easier. Once you develop your ViewModel, you can hand it over to a designer who can make it look very artsy :). MVVM Light adds some functionality to make Blend recognize your ViewModel. For the most part, you can do just about everything you want to in the ViewModel to affect the view.
If anyone reads this, I hope you find this helpful. If you have questions, please let me know. I used MVVM Light in this example, but you could do this without MVVM Light.
You can handle click on list view item like this:
<ListView.ItemTemplate>
<DataTemplate>
<Button BorderBrush="Transparent" Background="Transparent" Focusable="False">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataContext.MyCommand, ElementName=ListViewName}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Button.Template>
<ControlTemplate>
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
...
This worked for me.
Single-clicking a row triggers the code-behind.
XAML:
<ListView x:Name="MyListView" MouseLeftButtonUp="MyListView_MouseLeftButtonUp">
<GridView>
<!-- Declare GridViewColumns. -->
</GridView>
</ListView.View>
Code-behind:
private void MyListView_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
System.Windows.Controls.ListView list = (System.Windows.Controls.ListView)sender;
MyClass selectedObject = (MyClass)list.SelectedItem;
// Do stuff with the selectedObject.
}
I would also suggest deselecting an item after it has been clicked and use the MouseDoubleClick event
private void listBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
try {
//Do your stuff here
listBox.SelectedItem = null;
listBox.SelectedIndex = -1;
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
I couldn't get the accepted answer to work the way I wanted it to (see Farrukh's comment).
I came up with a slightly different solution which also feels more native because it selects the item on mouse button down and then you're able to react to it when the mouse button gets released:
XAML:
<ListView Name="MyListView" ItemsSource={Binding MyItems}>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListViewItem_PreviewMouseLeftButtonDown" />
<EventSetter Event="PreviewMouseLeftButtonUp" Handler="ListViewItem_PreviewMouseLeftButtonUp" />
</Style>
</ListView.ItemContainerStyle>
Code behind:
private void ListViewItem_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
MyListView.SelectedItems.Clear();
ListViewItem item = sender as ListViewItem;
if (item != null)
{
item.IsSelected = true;
MyListView.SelectedItem = item;
}
}
private void ListViewItem_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
ListViewItem item = sender as ListViewItem;
if (item != null && item.IsSelected)
{
// do stuff
}
}
Considering the code below:
xmlns:interactivity="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
...
<ToggleButton IsChecked="{Binding Path=IsGlobalControllerAttached}" Command="{Binding Path=AttachDetachGlobalControllerAction}" ToolTip="{Binding Path=GlobalControllerToolTip}" Visibility="{Binding Path=CanApplyDateFilter, Converter={StaticResource bool2VisibilityConverter}}" Style="{StaticResource toolBarToggleButton}">
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsGlobalControllerCreated}" Value="true" TrueState="Normal" FalseState="Disabled" />
</i:Interaction.Behaviors>
<Image Source="../../Common/Images/pin.png"/>
</ToggleButton>
I am trying to set VisualState of Toggle Button by binding it to some property in ViewModel.
Here, I am not able to find the Microsoft.Expression.Interactivity.dll in the "Add Reference" list. I am using VS 2010.
What am i missing? Do i need to install Expression blend to get this dll?
Also,
Is there any other way to get the job done? ( Changing VisualState of a control by biding it with some property of ViewModel).
Thanks for your interest.
We use Attached Properties to manage custom state changes on elements. These are then just bound to the view model.
e.g. for a "split screen" setting we do the following.
Create a DependancyProperty in a class called SplitScreen, with a property called Mode:
public class SplitScreen
{
public static readonly DependencyProperty ModeProperty =
DependencyProperty.Register("Mode",
typeof(SplitScreenMode),
typeof(UserControl),
new PropertyMetadata(SplitScreenMode.None,
new PropertyChangedCallback(OnScreenModeChanged)));
public static void SetMode(DependencyObject obj, SplitScreenMode value)
{
obj.SetValue(ModeProperty, value);
}
public static SplitScreenMode GetMode(Control obj)
{
return (SplitScreenMode)obj.GetValue(ModeProperty);
}
static void OnScreenModeChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as UserControl;
if (control != null)
{
if (control.Parent == null)
{
control.Loaded += (s, e) =>
{
ApplyCurrentState(control);
};
}
else
{
ApplyCurrentState(control);
}
}
}
[snip]
}
You might note our little trick to late-update the value when Attached Property is initially set (there is often no parent element until the page is fully loaded).
In the Xaml file attach the property to the required element like this:
lib:SplitScreen.Mode="{Binding SplitScreenMode}"
The key is to catch dependency property changes and get that to change the visual state of the attached element (this is the snipped part of the SplitScreen.cs file):
static public void ApplyCurrentState(Control control)
{
string targetState;
switch (GetMode(control))
{
case SplitScreenMode.Single:
targetState = SplitScreenModeName.Single;
break;
case SplitScreenMode.Dual:
targetState = SplitScreenModeName.Dual;
break;
default:
targetState = SplitScreenModeName.None;
break;
}
VisualStateManager.GoToState(control, targetState, true);
}
The alternative is to install the Expression Blend SDK
You do not need Expression Blend to make use of the SDK and all the cool extras. It is a lot less work for simple items (we just needed some custom behaviour it did not support).
I have the same command that I want to use for two controls on a dialog type window. As potentially interesting background, I'm using Josh Smith's ViewModel / RelayCommand ideas, since I am new to WPF and it's the first thing I've seen that I can actually understand from a big picture point of view.
So the command is a property of a ViewModel, and with the Button's built-in support, it is trivial and painless to bind to the command in the XAML:
<Button ... Command="{Binding Path=PickCommand}" Content="_Ok"></Button>
Now in a ListView, the only way I have gotten to use the same command hooked up to trigger on a double click is by using an event handler:
<ListView ...
ItemsSource="{Binding Path=AvailableProjects}"
SelectedItem="{Binding Path=SelectedProject, Mode=TwoWay}"
MouseDoubleClick="OnProjectListingMouseDoubleClick"
>
private void OnProjectListingMouseDoubleClick(object sender, MouseButtonEventArgs e) {
var vm = (ProjectSelectionViewModel) DataContext;
vm.Pick(); // execute the pick command
}
Is there a way to do this by binding the way the button does it?
Cheers,
Berryl
<------- implementation - is there a better way? --->
Your SelctionBehavior class was spot on, but I was confused at your xaml code. By setting the "Style" on the listViewItem I was getting the children of the DataContext where the command I want to execute lives. So I attached the behavior to the ListView itself:
<ListView ...Style="{StaticResource _attachedPickCommand}" >
And put the style in a resource dictionary:
<Style x:Key="_attachedPickCommand" TargetType="ListView">
<Setter Property="behaviors:SelectionBehavior.DoubleClickCommand" Value="{Binding Path=PickCommand}" />
</Style>
It works! But it 'feels' awkward setting the style property of the list view. Is this just because I am not comfortable with style as more than something visual in wpf or is there a better way to do this?
Cheers, and thanks!
Berryl
Yes there is! You can use attached behaviors and bind the command to that behavior.
public class SelectionBehavior {
public static readonly DependencyProperty CommandParameterProperty=
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(SelectionBehavior));
public static readonly DependencyProperty DoubleClickCommandProperty=
DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(SelectionBehavior),
new PropertyMetadata(OnDoubleClickAttached));
private static void OnDoubleClickAttached(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var fe=(FrameworkElement)d;
if(e.NewValue!=null && e.OldValue==null) {
fe.PreviewMouseDown+=fe_MouseDown;
} else if(e.NewValue==null && e.OldValue!=null) {
fe.PreviewMouseDown-=fe_MouseDown;
}
}
private static void fe_MouseDown(object sender, MouseButtonEventArgs e) {
if(e.ClickCount==2) {
var dep=(FrameworkElement)sender;
var command=GetDoubleClickCommand(dep);
if(command!=null) {
var param=GetCommandParameter(dep);
command.Execute(param);
}
}
}
public static ICommand GetDoubleClickCommand(FrameworkElement element) {
return (ICommand)element.GetValue(DoubleClickCommandProperty);
}
public static void SetDoubleClickCommand(FrameworkElement element, ICommand value) {
element.SetValue(DoubleClickCommandProperty, value);
}
public static object GetCommandParameter(DependencyObject element) {
return element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject element, object value) {
element.SetValue(CommandParameterProperty, value);
}
}
and in the xaml you would need to set a style for a ListViewItem which represents your data in the ListView. Example
<ListView>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="local:SelectionBehavior.DoubleClickCommand" Value="{Binding Path=DataContext.PickCommand}"/>
<Setter Property="local:SelectionBehavior.CommandParameter" Value="{Binding Path=DataContext}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Here is some more information about the Attached Behavior pattern