I'm quite new to WPF and C#. I'm trying to catch a double click event in an user control and handle it through an ICommand.
Here is how i'm trying to do that:
User Control:
<UserControl x:Class="MyNamespace.View.MyUserControl"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:MyNamespace.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
DataContext="{Binding MyViewModelClass, Source={StaticResource Locator}}"
>
<UserControl.Resources>
<DataTemplate x:Key="MyTemplate">
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DataContext.HandleDoubleClickCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<WrapPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="12" >
<TextBlock Text="Data: "><Run FontWeight="Light" Text="{Binding data}"/></TextBlock>
</WrapPanel>
</ContentControl>
</DataTemplate>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{DynamicResource ColorPanel2}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderThickness="0 0 0 1">
<WrapPanel Margin="12" VerticalAlignment="Center">
<TextBlock FontWeight="Bold" Margin="8" VerticalAlignment="Center" FontSize="18"
Text="some text: ">
</TextBlock>
</WrapPanel>
</Border>
<ListView Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding SelectedItem.listElement, UpdateSourceTrigger=PropertyChanged}"
ItemTemplate="{StaticResource MyTemplate}"
SelectedItem="{Binding SelectedItem}"
/>
</Grid>
</UserControl>
And i placed the command to handle the event in the ViewModel class:
ICommand _HandleDoubleClickCommand;
public ICommand HandleDoubleClickCommand
{
get
{
if (_HandleDoubleClickCommand == null)
{
_HandleDoubleClickCommand = new RelayCommand<object>(ExecuteHandleDoubleClickCommand, CanExecuteHandleDoubleClickCommand);
}
return _HandleDoubleClickCommand;
}
}
private bool CanExecuteHandleDoubleClickCommand(object arg)
{
return true;
}
private void ExecuteHandleDoubleClickCommand(object obj)
{
System.Windows.MessageBox.Show("HandleDoubleClickCommand");
}
So, basically i have a list of elements and i want to handle the double click event on is.
The behavior i see is that sometimes the HandleDoubleClickCommand gets executed, sometimes it's not. I don't see any exception thrown and i checked with the debugger that the code is executed only when the message box is shown.
I also tried to use InputBindings instead of Interaction.Triggers:
<ContentControl.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding DataContext.HandleDoubleClickCommand, RelativeSource={RelativeSource AncestorType=UserControl}, diag:PresentationTraceSources.TraceLevel=High}" />
But i see the same behavior. Enabling the TraceLevel=High didn't give me useful info..
Also, i tried to handle the right click event instead of the double click, but still, the command gets executed only sometimes.
I think i'm missing something here, could someone help me to understand what the issue is? or at least give me some advice on how i can debug the problem..
Thank you
UPDATE
I found out that the command gets always executed if i double click on the element text. The thing is that i would like it to be executed even if i double click on an empty space of the selected row of the ListView...
Move the Margin from the WrapPanel to the TextBlock and define an ItemContainerStyle that makes the contents of the ListViewItem stretch horizontally.
You should also set the Background property of the WrapPanel to capture the clicks:
<ListView Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding SelectedItem.listElement}"
SelectedItem="{Binding SelectedItem}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding DataContext.HandleDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<WrapPanel Background="Transparent">
<TextBlock Text="Data: " Margin="12"><Run FontWeight="Light" Text="{Binding data}"/></TextBlock>
</WrapPanel>
</ContentControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Related
In my C# WPF MVVM pattern application, I have an ItemsControl in my View that draws Lines and Buttons on a Canvas based on a bound ItemsSource, defined in XAML as below:
<Window.DataContext>
<viewmodels:MainWindowViewModel />
</Window.DataContext>
.
.
.
<ItemsControl
x:Name="DiagramViewCanvas"
ItemsSource="{Binding DiagramObjects, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:LineObject}">
<Line
X1="{Binding XStart}"
Y1="{Binding YStart}"
X2="{Binding XEnd}"
Y2="{Binding YEnd}"
Stroke="White"
StrokeThickness="1"
SnapsToDevicePixels="True"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ButtonObject}">
<Button
Style="{DynamicResource MyDiagramButtonStyle}"
Width="225"
Height="30"
Content="{Binding Content}"
FontSize="13"
SnapsToDevicePixels="True">
</Button>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding XPosition, UpdateSourceTrigger=PropertyChanged}" />
<Setter Property="Canvas.Top" Value="{Binding YPosition, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This code works completely fine. My question is how to bind the Buttons' Click event to a method in the ViewModel (MainWindowViewModel).
Option 1 (which I don't want to use due MVVM pattern): If I try a simple Click event definition as below ...
<Button
Style="{DynamicResource MyDiagramButtonStyle}"
Width="225"
Height="30"
Content="{Binding Content}"
FontSize="13"
SnapsToDevicePixels="True"
Click="OnButtonClick"/>
... where OnButtonClick is defined in my XAML codebehind, the OnButtonClick method is successfully called and executed for each Button that is created at runtime. It works fine.
Option 2: However, if I try to use Interaction.Triggers as below (which is the approach I regularly use without any problems in my code) to avoid placing code in code behind ...
<Button
Style="{DynamicResource MyDiagramButtonStyle}"
Width="225"
Height="30"
Content="{Binding Content}"
FontSize="13"
SnapsToDevicePixels="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction TargetObject="{Binding}" MethodName="OnButtonClick"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
... where OnButtonClick is defined in my MainWindowViewModel ...
public void OnButtonClick(object sender, RoutedEventArgs e)
{
if (sender is Button btn)
{
// do something
}
}
... I get the following error:
System.ArgumentException: 'Could not find method named 'OnButtonClick' on object of type 'ButtonObject' that matches the expected signature.'
Question 1: Am I making a basic mistake in my implementation of interaction triggers (I have many other interaction triggers in my code that work completely fine)? Or is it that Interaction.Triggers do not work in this scenario where the Buttons are created dynamically at runtime?
Question 2: Should I be using ICommand instead (for example as mentioned in Binding Commands to Events?)?
Thanks for any direction on what I am doing wrong.
Found a solution using Interaction.Triggers:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:CallMethodAction TargetObject="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=DataContext}" MethodName="OnButtonClick"/>
</i:EventTrigger>
</i:Interaction.Triggers>
I am trying to figure out how I can bind the ContextMenu of the Button that is being added in the ItemsControl I have. Basically, I'm wanting to be able to right click on a button and remove it from the observable collection that sits on my viewmodel. I understand that ContextMenu's are not part of the VisualTree, so using RelativeSource to walk up the tree to find my DataContext hasn't been useful to me.
The end goal of what I want to do is Bind the Command on the MenuItem to the RemoveCommand on my ViewModel and then pass in the Content property of the Button that you right click on so that I can remove it from the observable collection.
Any help on this would be greatly appreciated.
Model:
public class Preset
{
public string Name { get; set; }
}
ViewModel:
public class SettingsWindowViewModel
{
public ObservableCollection<Preset> MyPresets { get; } = new ObservableCollection<Preset>();
private ICommand _plusCommand;
public ICommand PlusCommand => _plusCommand ?? (_plusCommand = new DelegateCommand(AddPreset));
private ICommand _removeCommand;
public ICommand RemoveCommand => _removeCommand ?? (_removeCommand = new DelegateCommand<string>(RemovePreset));
private void AddPreset()
{
var count = MyPresets.Count;
MyPresets.Add(new Preset {Name = $"Preset{count+1}"});
}
private void RemovePreset(string name)
{
var preset = MyPresets.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
if (preset!= null)
{
MyPresets.Remove(preset);
}
}
}
XAML:
<Window x:Class="WpfTesting.Esper.Views.SettingsWindow"
x:Name="MainSettingsWindow"
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:viewModels="clr-namespace:WpfTesting.Esper.ViewModels"
mc:Ignorable="d"
Title="SettingsWindow" Height="470" Width="612">
<Window.DataContext>
<viewModels:SettingsWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="{x:Type MenuItem}" x:Key="PopupMenuItem">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border>
<ContentPresenter ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Width="70" Content="Load"/>
<Button Width="70" Content="Save As"/>
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Style="{StaticResource PopupMenuItem}" Header="Remove">
<!--
I need to set up binding a Command to a method on the DataContext of the Window, and I need to pass in the Content of the Button that is the parent of the ContextMenu
-->
</MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Width="20" Background="Transparent" BorderBrush="Transparent" Content="+" FontSize="21.333" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding PlusCommand}"/>
</StackPanel>
</Grid>
</Window>
Using WPF: Binding a ContextMenu to an MVVM Command as an introduction to what Tags can do, I figured out how to do what I was looking for by using multiple Tags to save the Context of what I was looking for.
I first made sure to give my window a x:Name
<Window x:Name="MainSettingsWindow"
Next, on the Button inside my DataTemplate of my ItemsControl, I set a Tag and set it to my Window
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
Next, in the ContextMenu, I seth the DataContext of the ContextMenu to the Tag I set on the Button, I also needed to create a Tag on the ContextMenu and point it back to the Content Property of the Button so that I can pass that into the CommandParameter
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
At this point, I can now bind my MenuItem correctly using the Command from my ViewModel and the Content Property from the Button
This is the final XAML for my ItemsControl:
<ItemsControl ItemsSource="{Binding MyPresets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="70" Content="{Binding Name}" Tag="{Binding ElementName=MainSettingsWindow}">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=Self}}" Tag="{Binding PlacementTarget.Content, RelativeSource={RelativeSource Mode=Self}}">
<MenuItem Header="Remove"
Style="{StaticResource PopupMenuItem}"
Command="{Binding Path=DataContext.RemoveCommand}"
CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
One thing to note is that I had to change the CommandParameter on my ViewModel to take an Object instead of a String. The reason I did this was because I was getting an exception on the CanExecute method in my DelegateCommand
This is the exception I was getting:
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'.
I'm not sure exactly what's causing that exception to throw, but changing it to Object works ok for me.
I had basically a similar problem, and the solution I found was to use the Messenger class some MVVM frameworks like Devexpress or Mvvm Light have.
Basically you can register in a viewModel to listen for incoming messages. The class itself, at least in the Devexpress implementation works with weak references, so you may not even unregister message handlers and it will not cause memory leaks.
I had used this method for removing on right click tabs from a ObservableCollection, so it was similar to your scenario.
You can have a look here :
https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-interaction-of-viewmodels-messenger.aspx
and here :
https://msdn.microsoft.com/en-us/magazine/jj694937.aspx
I have a problem with Menu Item Command Binding. I have used MVVM pattern
When I use right click, the menu appears. But when I click on the menu item doesn't work. Do you know why? Thanks
Here is the XAML:
<UserControl x:Class="PlotView.ViewModel.PlotViewControl"
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:oxy="http://oxyplot.org/wpf"
mc:Ignorable="d"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
d:DesignHeight="400" d:DesignWidth="600"
x:Name="theViewName">
<UserControl.Resources>
</UserControl.Resources>
<GroupBox Name="GB" Header="Graphs" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0">
<ListView Name="PlotLista" SelectedIndex="{Binding SelectedValue}" ItemsSource="{Binding PlotModelList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<ListView.ItemTemplate>
<DataTemplate>
<oxy:Plot MinHeight="260" Height="Auto" IsRendering="True" FontStyle="Normal" FontFamily="Arial" FontSize="8" VerticalContentAlignment="Top" HorizontalContentAlignment="Left" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=ActualWidth}" Model="{Binding }">
<oxy:Plot.ContextMenu>
<ContextMenu>
<MenuItem Header="Export to PNG" Command="{Binding DataContext.SavePNG, ElementName=theViewName}"/>
</ContextMenu>
</oxy:Plot.ContextMenu>
</oxy:Plot>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</GroupBox>
</UserControl>
Here are a small portion of ViewModel:
#region Fields
private readonly DelegateCommand _menuClick=new DelegateCommand(this.MenuItemClick);
#endregion
#region Command
public ICommand SavePNG
{
get { return this._menuClick; }
}
#endregion
private void MenuItemClick()
{
// some code here
}
Error:
System.Windows.Data Error: 40 : BindingExpression path error:
'SavePNG' property not found on 'object' ''PlotModel'
(HashCode=15385318)'. BindingExpression:Path=SavePNG;
DataItem='PlotModel' (HashCode=15385318); target element is 'MenuItem'
(Name=''); target property is 'Command' (type 'ICommand')
Your binding is trying to find SavePNG on your Item, not your ViewModel.
Instead, give your view an x:Name, or a Name, and use the following binding instead:
{Binding DataContext.SavePNG, ElementName=theViewName}
Assuming that SaveCommand is on the same ViewModel which contains your collection.
ContextMenus are a little different in wpf as they are not part of the visual tree of the control. As such they cannot 'see' any elements by relative source or by elementnames.
A little cheat is to use the PlacementTarget property and get the datacontext with that. Change your ListView to below to make it work. Notice the tag property on the ListView and the DataContext property on the ContextMenu.
<ListView Name="PlotLista" SelectedIndex="{Binding SelectedValue}" ItemsSource="{Binding PlotModelList}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Tag="{Binding DataContext,ElementName=theViewName}">
<ListView.ItemTemplate>
<DataTemplate>
<oxy:Plot MinHeight="260" Height="Auto" IsRendering="True" FontStyle="Normal" FontFamily="Arial" FontSize="8" VerticalContentAlignment="Top" HorizontalContentAlignment="Left" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=ActualWidth}" Model="{Binding }">
<oxy:Plot.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Export to PNG" Command="{Binding SavePNG}"/>
</ContextMenu>
</oxy:Plot.ContextMenu>
</oxy:Plot>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This wasn't written in visual studio so please check the syntax:
<ListView Tag="{Binding Path=DataContext,ElementName=theViewName}">
<ListView.ItemTemplate>
<DataTemplate>
<oxy:Plot Tag="{Binding Path=Tag,RelativeSource={RelativeSource AncestorType=ListView}">
<oxy:Plot.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Command="{Binding SavePNG}"/>
</ContextMenu>
</oxy:Plot.ContextMenu>
</oxy:Plot>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Plain and simple: I need to bind some properties of the ContextMenu items to a property of their parent in the DataTemplate.
I can't find a way to access it because ElementName doesn't work and RelativeSource allows me to use just Self or TemplatedParent.
Here's my code:
<telerikPrimitives:RadDataBoundListBox Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
ItemsSource="{Binding Transfers.Keys, Source={StaticResource TransfersManager}, Mode=OneWay}">
<telerikPrimitives:RadDataBoundListBox.ItemTemplate>
<DataTemplate>
<toolkit:TransferControl x:Name="TransferControl"
Header="{Binding Converter={StaticResource TransferMonitorToDocumentTitleConverter}}"
IsContextMenuEnabled="False"
Icon="{Binding Converter={StaticResource TransferMonitorToDocumentIconUriConverter}}"
AutoHide="False"
Monitor="{Binding}"
Language="it-IT"
StatusTextBrush="Black"
Foreground="Black">
<toolkit:TransferControl.HeaderTemplate>
<DataTemplate>
<StackPanel>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="item1"
IsEnabled="{Binding Monitor Property in the TransferControl object}"
/>
<toolkit:MenuItem Header="item2"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<Rectangle Fill="Transparent" Height="30"/>
<ContentControl Content="{Binding}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="Black" />
</StackPanel>
</DataTemplate>
</toolkit:TransferControl.HeaderTemplate>
</toolkit:TransferControl>
</DataTemplate>
</telerikPrimitives:RadDataBoundListBox.ItemTemplate>
What I'm trying to bind is:
<toolkit:MenuItem Header="item1"
IsEnabled="{Binding Monitor Property in the TransferControl object}"
/>
and I'd like to bind it to the Monitor property of the <toolkit:TransferControl x:Name="TransferControl" ... /> object.
I was able to solve this my simply creating a new UserControl that holds the content of your ListBox. I used the LongListSelector. Here is a solution that works.
First the XAML for the page:
<phone:LongListSelector Margin="0,0,-12,0" ItemsSource="{Binding Items}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<local:WindowsPhoneControl/>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
Then the UserControl. This was nothing special, just a wrapper.
<UserControl x:Class="PivotApp1.WindowsPhoneControl"
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:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480">
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<phone:LongListSelector x:Name="TransferControl" Margin="0,0,-12,0"
ItemsSource="{Binding Items}"
toolkit:TiltEffect.IsTiltEnabled="True">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="item1"
IsEnabled="{Binding DataContext.Monitor, ElementName=TransferControl}"/>
<toolkit:MenuItem Header="item2"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
</UserControl>
With this solution the Monitor property was hit when rendering the list. It will not be hit when opening the menu. Of course if you fire the propertyChanged for monitor, it will get the value again.
The property you want to bind to seems to be bound to the DataContext itself. Why not just bind to it (the DataContext) in the HeaderTemplate? All you need to do is bind the Header property to the DataContext, instead of converting it to something else, and then use the Converter inside the HeaderTemplate. Something like this:
<telerikPrimitives:RadDataBoundListBox.ItemTemplate>
<DataTemplate>
<toolkit:TransferControl x:Name="TransferControl"
Header="{Binding}"
IsContextMenuEnabled="False"
Icon="{Binding Converter={StaticResource TransferMonitorToDocumentIconUriConverter}}"
AutoHide="False"
Monitor="{Binding}"
Language="it-IT"
StatusTextBrush="Black"
Foreground="Black">
<toolkit:TransferControl.HeaderTemplate>
<DataTemplate>
<StackPanel>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="item1"
IsEnabled="{Binding}"
/>
<toolkit:MenuItem Header="item2"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<Rectangle Fill="Transparent" Height="30"/>
<ContentControl Content="{Binding Converter={StaticResource TransferMonitorToDocumentTitleConverter}}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="Black" />
</StackPanel>
</DataTemplate>
</toolkit:TransferControl.HeaderTemplate>
</toolkit:TransferControl>
</DataTemplate>
</telerikPrimitives:RadDataBoundListBox.ItemTemplate>
On a side note, I think you may be doing too much binding. Bindings and converters can lead to bad performance and sometimes it's just faster and easier to do things without bindings.
Btw, may I ask what does the TransferMonitorToDocumentTitleConverter do?
Update
It seems there is a bug in the toolkit. The Header property's getter casts the object to string and so, even though the property is of type object, you can only set it to a string - otherwise an exception is thrown in the getter, which is used internally.
Here's a possible workaround:
Extend the TransferControl like this:
public class TransferControlFixed : TransferControl {
public static readonly DependencyProperty HeaderFixedProperty = DependencyProperty.Register("HeaderFixed", typeof(object), typeof(TransferControlFixed), new PropertyMetadata(null));
public object HeaderFixed {
get {
return GetValue(HeaderFixedProperty);
}
set {
SetValue(HeaderFixedProperty, value);
}
}
public override void OnApplyTemplate() {
base.OnApplyTemplate();
var control = (ContentControl)this.GetTemplateChild("Header");
control.SetBinding(ContentControl.ContentProperty, new Binding() {
Path = new PropertyPath("HeaderFixed"),
Source = this
});
}
}
Use TransferControlFixed instead of TransferControl and bind the HeaderFixed instead of the Header property.
It seems like a reasonably simple workaround.
I'm trying to bind the SelectedItem to a View. But the view is not able to access the viewmodel when it is inside the Resources block.
When the datacontext is re-assigned to the children, the binding works for textblocks but not for UserControl (NoteView)
Am I missing any Binding?
PFB revised(entire) code and inline comments.
<UserControl x:Class="Konduva.View.NoteSearchView"
<!-- other namespaces here -->
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding NoteSearch, Source={StaticResource Locator}}">
<Grid>
<ListView ItemsSource="{Binding Notes}"
SelectedItem="{Binding SelectedNote}">
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:NoteViewModel}">
<DockPanel>
<TextBlock Text="{Binding Title}" />
<Popup Placement="Right"
PlacementTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListViewItem}}"
IsOpen="{Binding (ListViewItem.IsSelected), RelativeSource={RelativeSource FindAncestor, AncestorType=ListViewItem}}"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}">
<StackPanel>
<!-- This is working --> <TextBlock Text="{Binding SelectedNote.Title}" />
<!-- This is not working --> <v:NoteView DataContext="{Binding SelectedNote}" />
</StackPanel>
</Popup>
</DockPanel>
</DataTemplate>
</ListView.Resources>
</ListView>
</Grid>
</UserControl>
NoteView:
<Grid>
<TextBlock Text="{Binding Title}" /> // This Text is not displayed
</Grid>
Update 3
Since you're using MvvmLight: in NoteView, try changing
DataContext="{Binding Note, Source={StaticResource Locator}}"
to
<UserControl.Style>
<Style TargetType="UserControl">
<Setter Property="DataContext" Value="{Binding Note, Source={StaticResource Locator}}"/>
</Style>
</UserControl.Style>
Update 2
Encountered a similar problem a few minutes ago which I didn't fully understand so I'll throw in the same solution here to see if it helps. What happends if you change it to this?
<v:NoteView DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Popup}},
Path=DataContext.SelectedNote}"/>
Update
I'm unable to reproduce this. Try adding this in your NoteView constructor. Do you reach DataContextChangedHandler when you change the selection in the ListView?
public NoteView()
{
InitializeComponent();
DependencyPropertyDescriptor dpd =
DependencyPropertyDescriptor.FromProperty(UserControl.DataContextProperty,
typeof(UserControl));
if (dpd != null)
{
dpd.AddValueChanged(this, new EventHandler(DataContextChangedHandler));
}
}
void DataContextChangedHandler(object sender, EventArgs e)
{
MessageBox.Show("DataContext Changed: " + DataContext);
}
First answer
Your DockPanel will get the NoteViewModel as a DataContext and not the ListView and since this DataContext is inherited by all Childs every child will end up with a NoteViewModel as DataContext. To use the ListView as a DataContext for the Popup you can do this. I'm not sure what the DataContext Binding for the StackPanel does though, so I might be missing something here..
<DataTemplate DataType="{x:Type vm:NoteViewModel}">
<DockPanel>
<TextBlock Text="{Binding Title}" />
<Popup Placement="Right"
PlacementTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListViewItem}}"
IsOpen="{Binding (ListViewItem.IsSelected), RelativeSource={RelativeSource FindAncestor, AncestorType=ListViewItem}}"
DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}">
<StackPanel>
<TextBlock Text="{Binding SelectedNote.Title}" />
<StackPanel>
<v:NoteView DataContext="{Binding SelectedNote}"/>
</StackPanel>
</StackPanel>
</Popup>
</DockPanel>
</DataTemplate>
Instead of inserting a NoteView directly and binding the DataContext, use a ContentPresenter:
<ContentPresenter Content="{Binding SelectedNote}>
<ContentPresenter.ContentTemplate>
<DataTemplate>
<v:NoteView />
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>