WPF Treeview tooltip - dynamic - wpf

This is regards to MVVM & WPF -showing dynamic tooltip based on mouse location on a WPF Treeviewitems. Let say when the user hovers over a node which has got some business data based on that it should display tooltip.
Example: let say If i hover over manager items..should say -"locked" and for others "Ready to edit"..etc. Depends on the mouse over items data..tooltip text should guide the user to do further action. I'm trying to do some VM setting of tooltip text with the help of Tooltip opening event of TreeViewitem and trying to update the tooltip text..but facing some issue.
What are all the best and simple way to do it. Tried behavior and end up with some error.
System.Windows.Markup.XamlParseException occurred
Message="Cannot add content of type 'x.x.Views.CustomTreeView.TreeTooltipBehavior' to an object of type 'System.Windows.Interactivity.BehaviorCollection'. Error at object 'x.x.x.Views.CustomTreeView.TreeTooltipBehavior' in markup file 'x.x.x.Views;component/mypage.xaml' Line 72 Position 53."
Source="PresentationFramework"
Code:
<MyMyControls:ExtendedTreeView x:Name="MyTreeView" ItemsSource="{Binding MyDriveCollection}"
ItemContainerStyle="{StaticResource TVStyleTemplate}">
<MyMyControls:ExtendedTreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type NavModel:TreeDataItem}" ItemsSource="{Binding MyDriveCollection}">
<MyControls:SimpleEditableTextBlock x:Name="TabLabel" Text="{Binding Path=MenuText, Mode=TwoWay}"
ToolTip="{Binding MenuText,Mode=TwoWay}">
</MyControls:SimpleEditableTextBlock>
</HierarchicalDataTemplate>
</MyControls:ExtendedTreeView.ItemTemplate>
<MyControls:ExtendedTreeView.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding MenuText}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type TreeView}},Path=SelectedItem}" Command="{Binding Command}">
</MenuItem>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</MyControls:ExtendedTreeView.ContextMenu>
<i:Interaction.Behaviors>
<CustomTreeView:TreeTooltipBehavior CustomTreeView:ToolTipOpeningCommand="{Binding ToolTipOpeningCommand,Mode=TwoWay,diag:PresentationTraceSources.TraceLevel=High}" />
<CustomTreeView:WorkspaceTreeViewContextMenuBehavior ContextMenuOpeningCommand="{Binding ContextMenuOpeningCommand}"/>
</i:Interaction.Behaviors>
</MyControls:ExtendedTreeView>
TreeTooltipBehavior.cs
public class TreeTooltipBehavior : Behavior<ExtendedTreeViewItem>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.ToolTipOpening += new ToolTipEventHandler(AssociatedObject_ToolTipOpening);
}
void AssociatedObject_ToolTipOpening(object sender, ToolTipEventArgs e)
{
if (sender != null)
{
TreeDataItem hit = ((TreeDataItem) (((FrameworkElement) (sender)).DataContext));
if (ToolTipOpeningCommand != null)
{
ToolTipOpeningCommand.Execute(hit);
}
}
}
public static readonly DependencyProperty ToolTipOpeningCommandProperty = DependencyProperty.Register(
"ToolTipOpeningCommand",
typeof(ICommand),
typeof(TreeTooltipBehavior),
new PropertyMetadata(null));
public ICommand ToolTipOpeningCommand
{
get { return (ICommand)GetValue(ToolTipOpeningCommandProperty); }
set { SetValue(ToolTipOpeningCommandProperty, value); }
}
}
In my view model I'm expecting to handle the ToolTipOpeningCommand and declared enough to get the event via Behavior class.
interestingly the contextmenu behavior works fine but tooltip behavior throws xaml parser exception..
1) Am i defined at the right place (for behavior) ?
2) If Contextmenu behavior works then why not tooltipbehavior ?
3) Any clue from the exception pasted at the top ?
I'm expecting to,
(Xaml)-tooltip behavior -> to invoke tooltipopening event in the (behavior class) -> which in turns invoke the command defined in the ViewModel. I tried this similar way for context menu and works fine.
Pls provide some hint about fixing this issue.

The XAML parser error is because you are trying to attach a Behavior that only applies to ExtendedTreeViewItem to an ExtendedTreeView element. In other words, if you change Behavior<ExtendedTreeViewItem> to Behavior<ExtendedTreeView>, you will fix the parse error.
Although we cannot see from the code you presented, the reason the ContextMenu works is probably because WorkspaceTreeViewContextMenuBehavior derives from a Behavior with a generic type parameter that is compatible with ExtendedTreeView, such as FrameworkElement.

Related

WPF - clicking on a button inside an user control doesn't call the command, and neither does clicking on the control itself

I have a serious issue with binding any command to my user control. Everything compiles, but the command is never called. I have tried two approaches - first, I tried to bind the command to a button inside my control, and when I was unable to do it, I tried to bind the command to an inputcommand of the control itself to see if it would work. It didn't. The control itself is within an ItemsControl, in case that matters.
Here's a simplified version of what I did. In the xaml.cs file of the control:
public static readonly DependencyProperty CloseCommandProperty = DependencyProperty.Register(
"CloseCommand",
typeof(ICommand),
typeof(Thumbnail),
new UIPropertyMetadata(null)
);
public ICommand CloseCommand
{
get { return (ICommand)GetValue(CloseCommandProperty); }
set { SetValue(CloseCommandProperty, value); }
}
In the UserControl's xaml file, the offending button (the UserControl has Name="Control", and Hash is another dependency property):
<Button Command="{Binding ElementName=Control, Path=CloseCommand}" CommandParameter="{Binding ElementName=Control, Path=Hash}">
<TextBlock Text="X"/></Button>
Now, a simplified (irrelevant properties not included) datatemplate part of the xaml file of the view (which has a datacontext, if that matters), where I use this control:
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:Thumbnail Hash="{Binding Hash}"
CloseCommand="{Binding ElementName=Control, Path=DataContext.RemoveImageCommand}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
Just for the sake of completeness, I'll include the command from the viewmodel.
private bool CanRemoveImageCommandExecute(string hash)
{
return true;
}
private void RemoveImageCommandExecute(string hash)
{
MessageBox.Show("ABC","ABC");
}
public ICommand RemoveImageCommand
{
get { return new RelayCommand<string>(RemoveImageCommandExecute, CanRemoveImageCommandExecute);}
}
The RelayCommand class comes from MicroMVVM, and it just creates a command from two functions (and works everywhere else).
Can you tell me why clicking the button does nothing and how to fix it?
It seems that, even though I wasted a few hours on that, I was too quick to ask the question. Literally a few minutes after posting it, I realized that my binding in ItemTemplate is wrong.
The problem was that I used ElementName instead of RelativeSource:
CloseCommand="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:AddImage}
Where local:AddImage is the name of the view which has the DataContext set to the viewmodel..

<Image> source not working on binding

I am trying to bind a listbox via ItemsTemplate to a collection of custom "Document" objects but am having an issue while trying to bind an image to the Document.ImageResourcePath property. Here is my markup
<ListBox Name="lbDocuments">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=ImageResourcePath}"
Margin="5,0,5,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is my load event for the form that has the listbox.
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
List<Objects.Document> docs = Objects.Document.FetchDocuments();
lbDocuments.ItemsSource = docs;
}
My Document class holds a string to a resource image located in my resources folder depending on the document extension.
e.g. (this is part of a case statement within the document class)
case Cache.DocumentType.Pdf:
this.ImageResourcePath = "/JuvenileOrganizationEmail;component/Resources/pdf_icon.jpg";
break;
When the Window loads I get absolutely nothing in my listbox when it is bound to 23 perfectly well Document types. What could I be doing wrong?
Use an ObservableCollection instead of a List, and make the reference "class level" to your Window.
ObservableCollection<Objects.Document> _docs;
Make sure the DataContext is set in the Window's Ctor.
public Window()
{
_docs = new ObservableCollection<Objects.Document>(Objects.Document.FetchDocuments());
this.DataContext = this;
}
Then, you can just update your Window Loaded event:
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
lbDocuments.ItemsSource = _docs;
}
Or, an alternative solution, will be binding the ItemsSource of the ListBox directly to a public property of the collection. This is assuming the Ctor (above) is still used.
<ListBox Name="lbDocuments" ItemsSource={Binding Docs}>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=ImageResourcePath}" Margin="5,0,5,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In your Window.cpp file (though, a separate ViewModel class may be recommended if you are doing MVVM)
public ObservableCollection<Objects.Document> Docs
{
get { return _docs; }
}

How to select all the text when the edit textbox in a DataGridTemplateColumn receives focus?

I'm trying to get a DataGridTemplateColumn to behave identically to a TextColumn
when the cell goes into edit mode (Press F2), the user can immediately start typing in the new value
by default, existing text content is selected - so that you can set new values easily
Got the first one done ; however selecting all the text isn't working. As mentioned by a number of posts, tried hooking into the GotFocus event and selecting all the text in code-behind. This worked for a standalone textbox ; however for a Textbox which is the edit control for a TemplateColumn, this doesn't work.
Any ideas?
Code Sample:
<Window.Resources>
<Style x:Key="HighlightTextBoxStyle" TargetType="{x:Type TextBox}">
<EventSetter Event="GotFocus" Handler="SelectAllText"/>
<EventSetter Event="GotMouseCapture" Handler="SelectAllText"/>
<Setter Property="Background" Value="AliceBlue"/>
</Style>
<DataTemplate x:Key="DefaultTitleTemplate">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate x:Key="EditTitleTemplate">
<TextBox x:Name="Fox"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource HighlightTextBoxStyle}">
</TextBox>
</DataTemplate>
</Window.Resources>
<DockPanel>
<TextBox DockPanel.Dock="Top" x:Name="Test" Text="{Binding Path=(FocusManager.FocusedElement).Name, ElementName=MyWindow}"
Style="{StaticResource HighlightTextBoxStyle}"/>
<toolkit:DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTemplateColumn Header="Templated Title"
CellTemplate="{StaticResource DefaultTitleTemplate}"
CellEditingTemplate="{StaticResource EditTitleTemplate}" />
<toolkit:DataGridTextColumn Header="Title" Binding="{Binding Path=Title}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
</DockPanel>
Missed updating the post with an answer...
The problem seems to be that for a custom data grid column (aka a DataGridTemplateColumn) the grid has no way of knowing the exact type of the editing control (which is specified via a DataTemplate and could be anything). For a DataGridTextColumn, the editing control type is known and hence the grid can find it and invoke a SelectAll() in it.
So to achieve the end-goal for a TemplateColumn, you need to provide an assist. I forgotten how I solved it the first time around.. but here is something that I searched-tweaked out today. Create a custom derivation of a TemplateColumn with an override of the PrepareCellForEdit method as shown below (Swap Textbox with your exact editing control).
public class MyCustomDataColumn : DataGridTemplateColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var contentPresenter = editingElement as ContentPresenter;
var editingControl = FindVisualChild<TextBox>(contentPresenter);
if (editingControl == null)
return null;
editingControl.SelectAll();
return null;
}
private static childItem FindVisualChild<childItem>(DependencyObject obj)
}
Here's an implementation for FindVisualChild.
XAML:
<WPFTestBed:MyCustomDataColumn Header="CustomColumn"
CellTemplate="{StaticResource DefaultTitleTemplate}"
CellEditingTemplate="{StaticResource EditTitleTemplate}"/>
</DataGrid.Columns>
Lot of code for an annoying inconsistency.
I know this is way late but I took a different approach and creatively extended the TextBox class. I don't really like using the boolean to check if the text is already defined but the problem is that the selection events all fire before the text is set from the binding so SelectAll() doesn't have anything to select! This class is probably only useful as a editing template in something like a DataGridTemplateColumn. Every solution I found for this issue is pretty much a hack so I don't feel too bad about this one ... :)
class AutoSelectTextBox : TextBox
{
private bool _autoSelectAll= true;
protected override void OnInitialized(EventArgs e)
{
// This will cause the cursor to enter the text box ready to
// type even when there is no content.
Focus();
base.OnInitialized(e);
}
protected override OnKeyDown(System.Windows.Input.KeyEventArgs e)
{
// This is here to handle the case of an empty text box. If
// omitted then the first character would be auto selected when
// the user starts typing.
_autoSelectAll = false;
base.OnKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (_autoSelectAll)
{
SelectAll();
Focus();
_autoSelectAll= false;
}
base.OnTextChanged(e);
}
}
Kinda VERY late...just putting this out here in case someone can use this
I had a similar need to DeSelect (or Select All) text in a DataGridTextColumn on editing
Just added the method to the PreparingCellForEdit event of the DataGrid
DataGrid.PreparingCellForEdit += DataGrid_PreparingCellForEdit;
Then assigned the (e.EditingElement as TextBox) and then set my options
private void DataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
var txtBox = e.EditingElement as TextBox;
txtBox.Select(txtBox.Text.Length, 0); //to DeSelect all and place cursor at end
txtBox.SelectAll(); // to selectall
}

WPF databinding with a user control

I have a wpf user control, which exposes a single custom dependency property. Inside the user control, a textblock binds to the value of the dp. This databinding works in all scenarios except when the data source is an object.
The minimal code necessary to reproduce this is:
this is the main part of the user control
<StackPanel Orientation="Horizontal">
<TextBlock Text="**SimpleUC** UCValue: "/>
<TextBlock Text="{Binding UCValue}"/>
</StackPanel>
and the user control code behind:
public SimpleUC()
{
InitializeComponent();
this.DataContext = this;
}
public string UCValue
{
get { return (string)GetValue(UCValueProperty); }
set { SetValue(UCValueProperty, value); }
}
public static readonly DependencyProperty UCValueProperty =
DependencyProperty.Register("UCValue", typeof(string), typeof(SimpleUC), new UIPropertyMetadata("value not set"));
this is the test window. I imported my project xml namespace as "custom"
<Window.Resources>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="20"/>
</Style>
</Window.Resources>
<StackPanel>
<StackPanel>
<TextBlock Text="This fails to bind:"/>
<custom:SimpleUC UCValue="{Binding SomeData}"/>
</StackPanel>
<StackPanel>
<TextBlock>The same binding on a regular control like Label</TextBlock>
<Label Content="{Binding SomeData}"/>
</StackPanel>
<Slider x:Name="sld" />
<StackPanel>
<TextBlock>However, binding the UC to another element value, like a slider works</TextBlock>
<custom:SimpleUC UCValue="{Binding ElementName=sld,Path=Value}"/>
</StackPanel>
</StackPanel>
and the test window code behind is:
public TestWindow()
{
InitializeComponent();
this.DataContext = this;
}
//property to bind to
public string SomeData { get { return "Hello S.O."; } }
When I turn on the diagnostic tracing on the TestWindow, it spits out the error "BindingExpression path error:
'SomeData' property not found on 'object' ''SimpleUC' (Name='')' ... "
The binding expression is the same as the one I used in the neighboring label and it worked fine. This behavior seems really bizarre to me. Can anyone shed some light?
You set DataContext of your SimpleUC to itself here
public SimpleUC()
{
InitializeComponent();
this.DataContext = this; // wrong way!
}
so when you use binding here
<custom:SimpleUC UCValue="{Binding SomeData}"/>
it searches property SomeData in control's data context which is set to this object because code in SimpleUC constructor overrides value of DataContext and it is not set to TestWindow object anymore as you expected. That's why your solution works - it doesn't affect DataContext which is inherited from window. Also you can keep this.DataContext = this; but set element where to search property explicitly like this (skipped irrelevant)
<Window ... Name="wnd1">
<custom:SimpleUC UCValue="{Binding SomeData, ElementName=wnd1}"/>
...
But my oppinion is that your variant from the answer looks more convenient to me, setting data context to this is not very good practice.
Hope it helps.
If you must use a UserControl, your
<TextBlock
Text="{Binding RelativeSource={RelativeSource Self},
Path=Parent.Parent.UCValue}"
/>
is an ok way to do it and
<TextBlock
Text="{Binding UCValue,
RelativeSource={RelativeSource FindAncestor,custom:SimpleUC,1}}"
/>
is better because you don't rely on the control hierarchy and possible instantiation order issues.
However I would recommend for this kind of situation that you use "custom controls" instead of "user controls". They take a little bit of getting used to, but they are much more powerful because their XAML is the template itself which means you can use TemplateBinding and {RelativeSource TemplatedParent}.
In any case, DataContext = this; is definitely to be avoided.

Error when binding a Dependency Property on a custom Behavior

I am exploring the Silverlight attached behaviors mechanism in order to use the Model-View-ViewModel pattern within my Silverlight applications. To start with, I am trying to get a simple Hello World working, but I am completely stuck in an error for which I'm not able to find a solution.
What I have right now is a page that just contains a button which should display a message when clicked. The click event is handled by using a class derived from Behavior, and the message is specified as a dependency property of the behavior itself. The problem comes when trying to bind the message property to a property on a viewmodel class used as the data context: I get an exeption in the call to InitializeComponent in the view.
Here is all the code I'm using, as you can see it is rather simple. First the markup of the main page and the view it contains:
MyPage
<UserControl x:Class="MyExample.MyPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyExample"
>
<Grid x:Name="LayoutRoot">
<local:MyView/>
</Grid>
</UserControl>
MyView (the TextBlock is there just to check that the binding syntax is correct)
<UserControl x:Class="MyExample.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyExample"
Width="400" Height="300">
<StackPanel Orientation="Vertical" x:Name="LayoutRoot" Background="White">
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
</StackPanel.Resources>
<TextBlock Text="This button will display the following message:"/>
<TextBlock Text="{Binding MyMessage, Source={StaticResource MyResource}}" FontStyle="Italic"/>
<Button x:Name="MyButton" Content="Click me!">
<i:Interaction.Behaviors>
<local:MyBehavior Message="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</i:Interaction.Behaviors>
</Button>
</StackPanel>
</UserControl>
Now the code, there are two classes: one for the behavior and another one for the viewmodel:
MyViewmodel
public class MyViewmodel
{
public string MyMessage
{
get { return "Hello, world!"; }
}
}
MyBehavior
public class MyBehavior : Behavior<Button>
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(MyBehavior),
new PropertyMetadata("(no message)"));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Click -= new RoutedEventHandler(AssociatedObject_Click);
}
void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Message);
}
}
Simple enough, but this code throws an AG_E_PARSER_BAD_PROPERTY_VALUE [Line: 15 Position: 43] (right at the start of the value being set for the Message property) exception when ran. I'm sure that I'm missing something, but what?
Additional information: if I remove the binding from the Message property on MyBehavior (that is, if I set its value to any static string), it works fine. Also, I'm targeting silverlight 3 RTW.
Thanks a lot!
UPDATE
It seems that unlike WPF, Silverlight does not support data binding on any object deriving from DependencyObject, but only on objects deriving from FrameworkElement. This is no the case for Behavior, hence binding does not work.
I have found a workaround here, in the form of something named surrogate binders. Basically you specify the element and property to be binded, as well as the value, as attributes of the FrameworkElement containing the non-FrameworkElement object.
UPDATE 2
The surrogate binder does not work when the FrameworkElement contains an Interaction.Behaviors sub-element.
I have found another solution here, and this one seems to work. This time, the trick used is a DeepSetter. You define one of such setters as a static resource on the containing StackPanel, and then reference the resource from the behavior. So in my example, we should expand the StackPanel resources section as follows:
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
<local:DeepSetter
x:Key="MyBehaviorSetter"
TargetProperty="Message"
BindingExport="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</StackPanel.Resources>
...and modify the button's behavior declaration as follows:
<local:MyBehavior local:DeepSetter.BindingImport="{StaticResource MyBehaviorSetter}"/>
UPDATE 3
Good news: data binding for any DependecyObject will be available on Silverlight 4: http://timheuer.com/blog/archive/2009/11/18/whats-new-in-silverlight-4-complete-guide-new-features.aspx#dobind
To get the DataBinding support the class should inherit from FrameworkElement.Hoping MSFT will give support in Silverlight 4

Resources