I am having problem with the following code. I have a TreeView Control which is bound to a Collection. The TreeView does get populated with the desired results. HOwever the "IsSelected" property and ContextMenu's click Command is not firing. Following is the XAML code.
<UserControl x:Class="Plan.Views.PadView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:v="clr-namespace:Planner.Views"
xmlns:vm="clr-namespace:Planner.ViewModels"
<Grid>
<StackPanel Orientation="Vertical">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" OpacityMask="#FFECF5F5">
<TreeView ItemsSource="{Binding Pads}" Name="tree_View" Width="190">
<TreeView.ItemContainerStyle >
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding WellPadViewModel.IsSelected}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, Path=DataContext.RenameCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" >
<TextBlock.InputBindings>
<KeyBinding Key="F2" Command="{Binding RenameCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</UserControl>
And here is my ViewModel
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using System.ComponentModel;
using WPFApplication;
namespace FieldPlanner.ViewModels
{
public class PlanViewModel : BaseViewModel
{
Collection<Pads> pads = new Collection<Pads>();
public PlanViewModel()
{
IsSelected = true;
pads = new Collection<Pad>();
}
private ICommand _RenameCommand;
public ICommand RenameCommand
{
get
{
if (_RenameCommand == null)
{
_RenameCommand = new RelayCommand1((o) =>
{
// Your logic should go here
MessageBox.Show("Please rename me");
});
}
return _RenameCommand;
}
}
public ObservableCollection<PadInfo> Members { get; set; }
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree, otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
public static void OnSelectedItemChanged()
{
// Raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Class to hold the Pads info for a tree
/// </summary>
public class Pad
{
/// <summary>
/// Default Constructor
/// </summary>
public Pad()
{
this.Members = new ObservableCollection<PadInfo>();
}
/// <summary>
/// Name of the pad
/// </summary>
public string Name { get; set; }
/// <summary>
/// Members of the pad
/// </summary>
public ObservableCollection<PadInfo> Members { get; set; }
}
/// <summary>
/// Class to hold the well and slot IDs snapped to a pad
/// </summary>
public class PadInfo
{
/// <summary>
/// Slot ID
/// </summary>
public string SlotID { get; set; }
/// <summary>
/// Well ID
/// </summary>
public string WellID { get; set; }
}
public class RelayCommand1 : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand1(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand1(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
// [DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
How can I identify the issue?
You have two problems:
IsSelected:
<Setter Property="IsSelected" Value="{Binding WellPadViewModel.IsSelected}" />
In TreeViewItem DataContext is set to instance of Pad and Pad doesn't have property IsSelected You have to do sth like this:
<Setter Property="IsSelected" Value="{Binding DataContext.IsSelected, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}" />
Problem with ContextMenu is much more sirious. ContextMenu isn't in VisualTree so you cannot bind to RelativeSource. Solution is here WPF Relative source- Cannot find source for binding with reference
Best regards
Please set the Tag property in your DataTemplate to TreeViewItem. I have sth like this:
<DataTemplate>
<Grid Width="270" Height="20" Tag="{Binding DataContext, RelativeSource = {RelativeSource AncestorType={x:Type UserControl}}}">
...
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Edit">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<command:EventToCommand Command="{Binding Tag.YOURCOMMAND}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
It should work.
Related
TLDR:
How to manage a different IsExpanded/IsSelected state of the same ViewModel in multiple views?
Long Version:
I have a good working App with a huge TreeView. The app has a lot few functions that select, expand and collapse the TreeView items. So the ViewModel contains a IsExpanded/IsSelected property for each TreeNode (bound to the TreeViewItem by a Style).
Now I have the requirement to avoid to much scrolling in this Tree. So the idea is to provide a second TreeView (maybe more..) with a root element selected from the first Tree).
The problem is that both TreeView's depend on the same IsExpanded/IsSelected properties, but I want to control these states for each Tree differently) and I have no good idea how update the IsExpanded/IsSelected logic to make it work with multiple TreeViews.
All functions that manipulate these states know the in which Tree they are used, because they depend on a parent TreeVM ViewModel.
I thought to have a IsExpanded/IsSelected ObservableCollection but can't find a good way to bind the TreeViewItem-properties to the collection (two-way).
App.xaml:
<Application
x:Class="MultiTree.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiTree"
>
<Application.Resources />
<Application.MainWindow>
<local:MainWindow />
</Application.MainWindow>
</Application>
App.xaml.cs:
using System.Windows;
namespace MultiTree
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow.Loaded += (ws, we) => {
MainWindow.DataContext = new AppVM();
};
MainWindow.Show();
}
}
}
MainWindow.xaml:
<Window
x:Class="MultiTree.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiTree"
Title="MainWindow"
Width="800"
Height="450">
<Window.Resources />
<ItemsControl ItemsSource="{Binding Views}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TreeView
Margin="5"
Background="Gainsboro"
ItemsSource="{Binding Root.Nodes}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
AppVM.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MultiTree
{
public class Bindable : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class TreeNode : Bindable
{
public TreeNode(string name, IEnumerable<TreeNode> nodes = null)
{
Name = name;
if (nodes == null)
_nodes = new ObservableCollection<TreeNode>();
else
_nodes = new ObservableCollection<TreeNode>(nodes);
}
private string _name;
public string Name
{
get => _name;
set {
_name = value;
OnPropertyChanged();
}
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set {
_isSelected = value;
OnPropertyChanged();
}
}
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set {
_isExpanded = value;
OnPropertyChanged();
}
}
private ObservableCollection<TreeNode> _nodes;
public ObservableCollection<TreeNode> Nodes => _nodes;
}
/// <summary>
/// "Root" for each TreeView, known by all functions that expand/collapse&select
/// </summary>
public class TreeVM : Bindable
{
private TreeNode _root;
public TreeNode Root
{
get => _root;
set {
_root = value;
OnPropertyChanged();
}
}
}
public class AppVM : Bindable
{
/// <summary>
/// Contains all TreeNodes
/// </summary>
private readonly TreeNode RootNode;
public AppVM()
{
RootNode = new TreeNode("ROOT", new[] {
new TreeNode("A", new [] {
new TreeNode("B", new [] {
new TreeNode("C", new [] {
new TreeNode("D")
})
}),
new TreeNode("E", new [] {
new TreeNode("F", new [] {
new TreeNode("G", new [] {
new TreeNode("H"),
new TreeNode("I")
})
})
})
})
});
Views.Add(new TreeVM() { Root = RootNode });
Views.Add(new TreeVM() { Root = RootNode.Nodes.First() });
}
private ObservableCollection<TreeVM> _views = new ObservableCollection<TreeVM>();
/// <summary>
/// Each Tree can contain all or a subset of TreeNodes
/// </summary>
public ObservableCollection<TreeVM> Views
{
get => _views;
}
}
}
Any help is very welcome :-)
I try to bind my CheckBox into my commnd.
Base view model
public ViewModelBase()
{
SelectedFileCommand = new SelectedFileCommand(this);
}
<Page.DataContext>
<viewmodel:ViewModelBase/>
</Page.DataContext>
Command
public class SelectedFileCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public ViewModelBase ViewModel { get; set; }
public SelectedFileCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
}
}
My CheckBox
<CheckBox IsChecked="{Binding IsSelected}"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding SelectedFileCommand}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
I also Try:
<CheckBox DataContext="{Binding}"
<i:Interaction.Triggers>
<i:EventTrigger EventName="IsChecked">
<i:InvokeCommandAction Command="{Binding SelectedFileCommand}"
CommandParameter="CheckBox.IsChecked"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But my Execute function not called.
EDIT
I forgot to mention that this CheckBox is inside ListViewItem
Working solution
<CheckBox IsChecked="{Binding IsSelected}"
Command="{Binding DataContext.CheckBoxSelectedFileCommand, ElementName=mainView}"
CommandParameter="{Binding IsChecked}"/>
If the checkbox is in a listview when you say Command="{Binding SelectedFileCommand}" you will bind to the listview item's datacontext. If yor command is in the viewmodel of your window this won't work. Something like this will bind to the command that is in your main viewmodel.
Command="{Binding DataContext.SelectedFileCommand, ElementName=mainView}"
Here I gave the window x:Name=mainView. This way I can bind to properties of it's dataContext.
And, IsChecked is not an event you should use "Checked".
Last, the command parameter issue. Since there are two different events for checkbox (Checked/Unchecked) you can use two commands and not pass any parameters. Or you can put a property in the list item viewmodel like;
public bool IsChecked { get; set; }
and you can bind your checkbox's IsChecked property to this property. And finally you can bind command parameter to this new property.
Edit: Full example
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="mainView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsChecked}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.SelectedFileCommand, ElementName=mainView}"
CommandParameter="{Binding IsChecked}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
Codebehind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApp2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; } = new ObservableCollection<ItemViewModel>();
public ICommand SelectedFileCommand { get; set; }
public MainViewModel()
{
SelectedFileCommand = new SelectedFileCommand(this);
this.Items.Add(new ItemViewModel() { Text = "Item 1" });
this.Items.Add(new ItemViewModel() { Text = "Item 2" });
this.Items.Add(new ItemViewModel() { Text = "Item 3" });
this.Items.Add(new ItemViewModel() { Text = "Item 4" });
}
}
public class ItemViewModel
{
public string Text { get; set; }
public bool IsChecked { get; set; }
}
public class SelectedFileCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public MainViewModel ViewModel { get; set; }
public SelectedFileCommand(MainViewModel viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
var x = parameter;
}
}
}
How can create layout dynamically in WPF (MVVM Pattern)? Scenario is as follows:
Something like a application for camera viewer,
At startup there is a main view with a button in the top of screen with label("Add camera"),When camera is added, it will be display in whole of main screen, after selecting second camera, screen should be divided into two parts, after selecting third camera, screen should be divided into third parts and so on.
How can do it in WPF?
Use listview and customize item panel to UniformGrid
<ListView ItemsSource="{Binding}" VerticalAlignment="Stretch" FlowDirection="LeftToRight" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Hidden">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid ClipToBounds="True"></UniformGrid>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate >
<Border BorderThickness="2">
<DockPanel Background="Red" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Id}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"></TextBlock>
</DockPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code Behind
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<Camera> cameraList = new ObservableCollection<Camera>();
public MainWindow()
{
InitializeComponent();
this.DataContext = cameraList;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int sn = cameraList.Count + 1;
cameraList.Add(new Camera() { Id = sn.ToString()});
}
}
public class Camera
{
public string Id { get; set; }
}
Ok this is just a little example. I used a viewmodel, with InotifyPropertyChanged implemented, a button with a Command binded and triggers on the column of the grid. Here is the working example
XAML
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow">
<Window.DataContext>
<local:MyViewModel></local:MyViewModel>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Add Camera" Command="{Binding AddCamera}" Margin="10" VerticalAlignment="Center" FontSize="15" Width="90" Height="26" FontWeight="DemiBold"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions >
<ColumnDefinition >
<ColumnDefinition.Style>
<Style TargetType="{x:Type ColumnDefinition}">
<Setter Property="Width" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding Camera1}" Value="True">
<Setter Property="Width" Value="*" />
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition >
<ColumnDefinition.Style>
<Style TargetType="{x:Type ColumnDefinition}">
<Setter Property="Width" Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding Camera2}" Value="True">
<Setter Property="Width" Value="*" />
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text=" CAMERA1" Width="100" HorizontalAlignment="Center" Height="100" Grid.Column="0"/>
<TextBlock Text=" CAMERA2" Width="100" HorizontalAlignment="Center" Height="100" Grid.Column="1"/>
</Grid>
</Grid>
</Window>
As you can see i placed 2 textblock in the inner grid to represent your "cameras"
now the viewmodel
-Viewmodel
namespace WpfApp2
{
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool camera1 = false;
private bool camera2 = false;
public bool Camera1
{
get { return camera1; }
set
{
camera1 = true;
NotifyPropertyChanged();
}
}
public bool Camera2
{
get { return camera2; }
set
{
camera2 = true;
NotifyPropertyChanged();
}
}
private RelayCommand addCamera;
private void Add()
{
if (Camera1 == false)
{
Camera1 = true;
}
else
Camera2 = true;
}
public ICommand AddCamera
{
get
{
addCamera = new RelayCommand(() => Add());
return addCamera;
}
}
}
}
If you understand MVVM you shouldn't be surprised by the viewmodel i presented above
as Extra an utility that i use to implement Commands
-Relay Command
namespace WpfApp2
{
internal class RelayCommand<T> : ICommand
{
#region Fields
readonly Action<T> _execute = null;
readonly Predicate<T> _canExecute = null;
#endregion // Fields
#region Constructors
public RelayCommand(Action<T> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </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 // Constructors
#region ICommand Members
[DebuggerStepThrough]
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 // ICommand Members
}
/// <summary>
/// A command whose sole purpose is to
/// relay its functionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
/// </summary>
internal class RelayCommand : ICommand
{
#region Fields
readonly Action _execute;
readonly Func<bool> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute();
}
#endregion // ICommand Members
}
}
Basically everything work around the DataTrigger inserted in the ColumnDefinition style. Note that when you press add camera, the Column will take all the available space thanks to Width = "*", so that every time you add a camera, each one will take the same amount of space. Of course this is just an example and you should work on it to add features like remove camera, add the opposite trigger (to put again the width to 0) etc. , but it's just to give you an idea.
P.S.: Someone will tell you that assign the datacontext as i did, is the biggest mistake you can do in MVVM. I don't agree with this, however, you have to find your way, when working with MVVM and i used datacontext as i did just to write this example faster
I want to implement (file) Explorer like icon display. The items have date and label.
User should be able to edit the label:
Select an item
Click on label
Label's TextBlock is replaced with TextBox for editing
How to end editing (just for info):
Click anywhere outside of the TextBox
Press Enter keyboard key (by implementing ICommand?)
1st I tried to set the Visibility of TextBlock and TextBox in code found out it is not the 'right' way to to do. Maybe it is possible to edit item's Label using (Data)Triggers?
I can track the OnClickLabelBlock and set selectedMedia.IsEditing = true; but it does not fire the trigger.
Any idea why MediaItem.IsEditing property value change is notifying the DataTrigger? Is it something to do with the order of execution or priority mechanism?
I will pick the answer which guides me to the 'best' architecture to solve it.
Thanks.
XAML:
<Window x:Class="WPFComponents.DailyImages"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Model="clr-namespace:WPFComponents.Model"
Title="Media Items" Height="300" Width="300">
<ListView x:Name="_mediaItemList" ItemsSource="{Binding MediaItems}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="Model:MediaItem">
<Grid Width="80" Margin="4">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Image HorizontalAlignment="Center" Stretch="Uniform" Source="{Binding Path=IconPath}" Width="70" />
<StackPanel Grid.Row="2">
<TextBlock Text="{Binding Path=Date}" TextWrapping="Wrap" />
<TextBlock x:Name="_labelTextBlock" Text="{Binding Path=Label}" TextWrapping="Wrap"
PreviewMouseLeftButtonDown="OnClickLabelBlock">
</TextBlock>
<TextBox x:Name="_labelTextBox" Text="{Binding Path=Label}" Visibility="Collapsed"
TextWrapping="WrapWithOverflow" TextAlignment="Center">
</TextBox>
</StackPanel>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter TargetName="_labelTextBlock" Property="Visibility" Value="Collapsed" />
<Setter TargetName="_labelTextBox" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Source:
public partial class DailyImages
{
public DailyImages()
{
InitializeComponent();
ViewModel.DailyImages dailyImages = new ViewModel.DailyImages();
// DailyImages has ObservableCollection<MediaItem> MediaItems property
_mediaItemList.DataContext = dailyImages;
}
private void OnClickLabelBlock(object sender, MouseButtonEventArgs e)
{
TextBlock notes = sender as TextBlock;
if (notes == null)
return;
MediaItem selectedMedia = notes.DataContext as MediaItem;
if (selectedMedia == null)
{
// TODO: Throw exception
return;
}
_mediaItemList.SelectedItems.Clear();
selectedMedia.IsSelected = true;
selectedMedia.IsEditing = true;
}
public class MediaItem
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
public bool IsEditing { get; set; }
public bool IsSelected { get; set; }
}
References:
Dependency Property Value Precedence
Part II: ListView & File Explorer Like Behaviour
MediaItem must implement INotifyPropertyChanged and each of its properties that must be bound, must call RaisePropertyChanged in order for the binding to work correctly. In your case, the Binding on IsEditing has no way to know that the value has changed.
To bind your IsEditing property, WPF has to be notified when it is modified.
Then you have to implement INotifyPropertyChanged in MediaItem. (Or add dependency properties)
public class MediaItem : INotifyPropertyChanged
{
public MediaItem()
{
IsEditing = false;
IsSelected = false;
}
// Use the same pattern for Date, Label & IconPath if these value may change after the MediaItem instance has been added to the collection MediaItems.
public DateTime Date { get; set; }
public string Label { get; set; }
public string IconPath { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private bool isEditing;
public bool IsEditing
{
get { return isEditing; }
set
{
if (isEditing != value)
{
isEditing = value;
OnPropertyChanged("IsEditing");
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Otherwise, your code is correct.
I want to know how to attach command to RibbonMenuButton item.
The following is my initial attempt but the command is never called.
<ribbon:RibbonWindow x:Class="RibbonMenuDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
Title="MainWindow"
x:Name="RibbonWindow"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ribbon:Ribbon x:Name="Ribbon">
<ribbon:RibbonTab x:Name="HomeTab"
Header="Home">
<ribbon:RibbonGroup x:Name="Group1"
Header="Map">
<ribbon:RibbonMenuButton ItemsSource="{Binding Regions}"
LargeImageSource="Images\LargeIcon.png"
Label="Regions" >
<ribbon:RibbonMenuButton.ItemContainerStyle >
<Style TargetType="MenuItem" >
<Setter Property="Command" Value="{Binding RegionChangeCommand}" />
<Setter Property="CommandParameter" Value="{Binding Label}"></Setter>
</Style>
</ribbon:RibbonMenuButton.ItemContainerStyle>
</ribbon:RibbonMenuButton>
</ribbon:RibbonGroup>
</ribbon:RibbonTab>
</ribbon:Ribbon>
</Grid>
</ribbon:RibbonWindow>
here is my code
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Windows.Controls.Ribbon;
using System.ComponentModel;
using System.Diagnostics;
namespace RibbonMenuDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : RibbonWindow
{
RelayCommand regionChangeCommand;
public MainWindow()
{
InitializeComponent();
this.DataContext = new Map();
}
public RelayCommand RegionChangeCommand
{
get
{
if (regionChangeCommand == null)
regionChangeCommand = new RelayCommand(param => OnRegionChange(param), param => false);
return regionChangeCommand;
}
}
private void OnRegionChange(object param)
{
var val = (string)param;
MessageBox.Show(val);
}
}
public class Map
{
public Map()
{
Regions = new List<string>
{
"EAST", "North", "West", "South"
};
}
public List<string> Regions
{
get;
set;
}
}
public class RelayCommand : ICommand
{
readonly Action<object> execute;
readonly Predicate<object> canExecute;
/// <summary>
/// create new simple command
/// </summary>
/// <param name="execute">execute handler</param>
/// <param name="canExecute">predicate to determin if can excute</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null)
throw new ArgumentNullException("execute handler required");
this.execute = execute;
Predicate<object> v = (x) => { return true; };
this.canExecute = canExecute ?? v;
}
public void Execute(object parameter)
{
this.execute(parameter);
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
Solved like this:
<ribbon:RibbonMenuButton DataContext="{Binding .}" ItemsSource="{Binding Regions}"
LargeImageSource="Images\LargeIcon.png" Label="Regions">
<ribbon:RibbonMenuButton.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.RegionChangeCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type
ribbon:RibbonMenuButton}}}" />
<Setter Property="CommandParameter" Value="{Binding Label}"></Setter>
</Style>
</ribbon:RibbonMenuButton.ItemContainerStyle>
</ribbon:RibbonMenuButton>
Adapted from the Click event routing on RibbonButton under RibbonMenuButton? page on the Visual Studio Forum on MSDN.