Send current Item and checkbox value in command parameters - wpf

I have a TreeView setup with a HierarchialDataTemplate. It's ItemsSource is bound to a collection of Overlay objects in my viewmodel, where each Overlay has a collection of Layer objects (thus the HierarchialDataTemplate). For each Overlay, I'm displaying a CheckBox and a Label which is simply bound to the Overlay's Name property.
What I'm trying to do is, each time one of the checkboxes is checked/unchecked, the current Overlay and the IsChecked property of the CheckBox will be sent as command parameters to my viewmodel.
If I'm not using the MultiValueConverter, I can send one of the properties fine. But I need to send both as parameters.
Below is the related .xaml for the treeview. I'm only showing the necessary parts and just the Checked trigger because the Unchecked is exactly the same:
<TreeView ItemsSource="{Binding OverlaysViewSource}" Name="LayersTreeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Layers}" >
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding DataContext.SetVisibilityCmd, RelativeSource={RelativeSource AncestorType=UserControl}}" >
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource multiValueConverter}">
<Binding Path="IsChecked, RelativeSource={RelativeSource AncestorType=CheckBox}" />
<Binding/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
<Label Content="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So in the MultiBinding, the first one: <Binding Path="IsChecked, RelativeSource={RelativeSource AncestorType=CheckBox}" /> to try and send the checkbox's IsChecked property. However, the value I'm getting in the command is DependencyProperty.UnsetValue.
The second one is just for the current Overlay item, but the whole TreeView is being sent as a parameter.
Update:
The Overlay class is a third party control and is used in a lot of places that I can't modify. So I can't just add a property to it.
Update2: I've managed to get the Overlay to send properly. Just need the IsChecked property now.

The binding for IsChecked should use {RelativeSource Self}, since the binding is being applied to the CheckBox via the Style.
Your update to your question shows you've already solved the other one.

Related

Can a WPF DataTrigger Deactivate a KeyBinding?

I've got a ListView with some KeyBindings that let the user move and delete entries with keyboard shortcuts. However, I don't want the bindings to be accessible all the time.
The button controls to add, remove, and move entries have their visibility tied to the selection of a ComboBox (only certain users can edit). I want the keyboard shortcuts to deactivate based on the box selection as well.
I haven't been able to find any info on whether or not this is possible yet. What do you guys think?
<ComboBox x:Name="TesterIdentityBox" ItemsSource="{Binding Path=TesterIdentityList, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" SelectedItem="{Binding Path=TesterIdentitySelection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="{Binding TesterIdentityIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding TestViewList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedIndex="{Binding SelectedTestIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SelectedTest}">
<ListView.InputBindings>
<KeyBinding Key="Up" Command="{Binding Path=MoveTestUpCommand}" CommandParameter="{Binding Path=SelectedTest.Description}" />
<KeyBinding Key="Down" Command="{Binding Path=MoveTestDownCommand}" CommandParameter="{Binding Path=SelectedTest.Description}" />
<KeyBinding Key="Delete" Command="{Binding Path=RemoveTestCommand}" />
</ListView.InputBindings>
I used Style Setters with DataTriggers to alter the command buttons' visibility, but I don't know what (if anything) is the equivalent for a non-visual element like a KeyBinding.
The simplest way in this case would be to implement the CanExecute() methods in your MoveTestUpCommand, MoveTestDownCommand and RemoveTestCommand. This methods should return false when you don't want the user to be able to do those things. So, your KeyBindings will have no effect since the commands will not be executed.
If your buttons' Command properties are also bound to these commands, these buttons will then update their availability (IsEnabled property) automagically according to the CanExecute() return values. To update the view state from the viewmodel, simply call the RaiseCanExecuteChanged() methods on corresponding commands (this depends on your ICommand implementation however).
To set the button's visibility according to its availability, you could use something like:
<Button
Command = "{Binding SampleCommand}"
Visibility = "{Binding IsEnabled, RelativeSource = {RelativeSource Self}, Converter = {StaticResource BooleanToVisibilityConverter}}"/>
There is an implementation of the BooleanToVisibilityConverter in System.Windows.Controls.

Event trigger is not working inside ItemControl

I have a Item control which fills by a list, and list is collection of two parameters 'Time' and 'Description'. For it, i am using a HyperLinkButton for time and a Label for description.
What i want is that, i want to create click event using EventTrigger of hyperLink button in Main viewModel. My code is:
<ItemsControl
x:Name="transcriptionTextControl"
ItemsSource="{Binding MyCollectionOfTranscription, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<HyperlinkButton Content="{Binding Time}">
<ToolTipService.ToolTip>
<ToolTip Content="Time"/>
</ToolTipService.ToolTip>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction
Command="{Binding HyperLinkButtonCommand}"
CommandParameter="{Binding
ElementName=transcriptionTextControl }" />
</i:EventTrigger>
</i:Interaction.Triggers>
</HyperlinkButton>
<sdk:Label Content="{Binding Description}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When i build project, it doesn't give error but ICommand for hyperLink, shows warning as 'Cannot resolve symbol HyperLinkButtonCommand', while this event trigger is working fine outside this .
Not getting, what is actual problem behind it, plz give your valuable suggestion...
First off,
<i:InvokeCommandAction
Command="{Binding HyperLinkButtonCommand}"
CommandParameter="{Binding
ElementName=transcriptionTextControl }" />
The Binding is trying to locate a property called HyperLinkButtonCommand on the instance of the type that is contained within MyCollectionOfTranscription (you don't need to bind to this two-way).
(Side note, sending an ItemsControl into your Command is not MVVM.)
The ItemsControl iterates through each element in this collection, creates a copy of the template defined in ItemsControl.ItemTemplate, and sets the BindingContext equal to this element (I assume its a Transcript). You can tell this from the warnings you get from the binding failing to find your HyperLinkButtonCommand if you crank up databinding debug settings.
Assuming
HyperLinkButtonCommand is a command defined in your ViewModel, and
The root of this xaml is a Window (could be a UserControl, but am assuming here)
Your ViewModel is the DataContext of the Window
you can change the binding to the following and it should work (or you should get a clue from it)
<i:InvokeCommandAction
Command="{Binding HyperLinkButtonCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding
ElementName=transcriptionTextControl }" />
I prefer just to give my root an x:Name of "root" and use "ElementName=root" in cases like this.

Handling listbox item selected

Seems like this would be a common problem...
I'm using MVVM Light
I have a listbox defined in my xaml
<ListBox ItemsSource="{Binding Events}" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand Command="{Binding EventPageCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="0,0,0,22">
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Preview}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This works all fine and dandy, except the user is able to click below this list and the event is still fired.
I can modify it to use SelectionChanged such as:
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding EventPageCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But then if I use the back button, I cannot select the same item twice since the selection hasn't actually changed. I could probably go into my codebehind and set the selected index = -1 on page load, but this seems like quite a hack, and I use list boxes in quite a few places and I would hate to have to remember to do this hack everywhere.
Any ideas?
edit
I added background color to my listbox and my listbox items and while my listbox items only take up what space the text occupies, the listbox takes up the entire screen, extending below the last listbox item. Perhaps one way would be to shore up this extra space.
update
what seems to work best so far is to set the VerticalAlignmnet="Top" on the listbox (this shores up the extra space on the bottom, not sure why, but then scrolling ends at the bottom of the list and not the bottom of the screen). I then disabled the scrolling on the listbox itself ScrollViewer.VerticalScrollBarVisibility="Disabled" and wrapped the whole thing in a <ScrollViewer>
Currently you are looking for the Tap event on the whole of the list box, not a individual item.
This is how I typically I use interaction triggers:
<DataTemplate DataType="{x:Type ViewModel:DataItem}" x:Key="ItemTemplate">
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding Tap}"/>
</i:EventTrigger>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction
Command="{Binding RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}},
Path=DataContext.KeyUpCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox Text="{Binding Name}"/>
</ContentControl>
</DataTemplate>
In this example the Tap event binds to a command on the DataContext (a data item) of an ItemsControl, whereas the KeyUp event binds to a command on the parent DataContext of the ListBox.
If all you want is to get an event when the user taps an item, why don't you use the trigger code you have on the Border element of the template?
You probably will have to use the Source property to bind the command to the ViewModel, but that can also be easely achieved (will just depend on how you are setting the Page DataContext right now)

wpf treeview selected item

I have a treeview:
<TreeView>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=TucActivity}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="DisplayedStartTime"></Binding>
<Binding Path="Name"></Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Message}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
bounded to Observable Collection object:
MainTreeView.ItemsSource = ((App)Application.Current).TucOC;
I want that every time the ((App)Application.Current).TucOC is updated
the selected item (and also the focus) will be the one in the observable collection.
I would like to do it in one place since the ((App)Application.Current).TucOC is
updated in multiple places in the code.
What's the best option to do it?
If you're using a development pattern like MVVM, I would create a property on your ViewModel class that's of the type held in the ObservableCollection, to hold the currently selected item for your treeview source. That would look something like this:
private object _selectedTuc;
public object SelectedTuc
{
get
{
return _selectedTuc;
}
set
{
_selectedTuc = value;
OnPropertyChanged("SelectedTuc");
}
}
Then, in your treeview, you bind this property to the treeview's SelectedItem:
<TreeView ItemsSource="{Binding TucOC, Mode=OneWay}" SelectedItem="{Binding SelectedTuc, Mode=TwoWay}">...</TreeView>
Notice on the binding for SelectedItem you specify a Mode value of TwoWay - this allows for your SelectedTuc property to be updated from the UI, as well as the UI being updated whenever the SelectedTuc property changes.
If you're not using MVVM or something like it, you're going to need to create a utility method that will update the TreeView's SelectedItem every time the selected item or index in your ObservableCollection changes. This is not, however, the way I would recommend doing it.

WPF Treeview contextmenu IsChecked binding MVVM

I've got a TreeView to which I associate a ContextMenu. That contextmenu has an item whose IsChecked property I want to bind to my ViewModel. Since I am using a tree each treeitem is bound to a subproperty of my ViewModel.
In the VS2010 output window I am seeing this databinding error:
BindingExpression path error: 'IsAutoStart' property not found on 'object' ''HostMgmtViewModel' (HashCode=12565727)'. BindingExpression:Path=IsAutoStart; DataItem='HostMgmtViewModel'
This clearly shows it is trying to bind to my ViewModel and not to the treeitem's associated data. How do I bind to the correct object? Remember my contextmenu is associated with the whole TreeView not to the specific treeitem.
---------- Edit
As xandy pointed out below the resolution to my problem was to bind the IsChecked like this:
{Binding Path=PlacementTarget.SelectedItem.IsDisabledStart, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}
<TreeView Name="tview" Grid.Row="0" Tag="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Name="miC" Header="{Binding Path=Tag.Key}" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
This is the working code snippet I have. Courtesy of [this].1 All you need is to change the binding path in the tag. I am currently binding the Treeview to a dictionary, so it is the Key property of it. It should not have any problem in binding to any object collections. One interesting finding is context menu is not in part of element tree and this cause the problem. I could bind the text box with no problem:
<TextBlock Grid.Row="1" DataContext="{Binding ElementName=tview, Path=SelectedItem}">
<TextBlock.Text>
<Binding Path="Key" />
</TextBlock.Text>
</TextBlock>
But it is not functioning if for menuitem if I put the same thing.

Resources