How to call Items.Refresh()? - wpf

I am making a filter for a collection.
I know how to do it using CollectionViewSource.
But I wanted to do it without using CVS.
According to my ideas, there is a CollectionView in the ItemsControl.Items property and you can use the methods of this property.
The filter can be added without problems.
But after calling Items.Refresh() nothing changes.
Simple example:
<UniformGrid Columns="2">
<FrameworkElement.Resources>
<sc:StringCollection
x:Key="coll">
<sys:String>112</sys:String>
<sys:String>22</sys:String>
<sys:String>33</sys:String>
<sys:String>114</sys:String>
<sys:String>411</sys:String>
</sc:StringCollection>
<CollectionViewSource
x:Key="cvs"
Source="{Binding Mode=OneWay, Source={StaticResource coll}}"
Filter="OnFilterCV"/>
</FrameworkElement.Resources>
<TextBox x:Name="tBox"
Text="1"
TextChanged="OnTextChanged"
VerticalAlignment="Center"/>
<TextBox x:Name="tBoxCV"
Text="1"
TextChanged="OnTextChangedCV"
VerticalAlignment="Center"/>
<ItemsControl x:Name="iCtrl"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource coll}}">
</ItemsControl>
<ItemsControl x:Name="iCtrlCV"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource cvs}}">
</ItemsControl>
</UniformGrid>
public partial class MainWindow : Window
{
private readonly CollectionViewSource cvs;
public MainWindow()
{
InitializeComponent();
iCtrl.Items.Filter = OnFilter;
cvs = (CollectionViewSource)iCtrlCV.FindResource("cvs");
}
private bool OnFilter(object obj)
{
if (string.IsNullOrWhiteSpace(tBox.Text))
return true;
string item = (string)obj;
return item.Contains(tBox.Text, StringComparison.OrdinalIgnoreCase);
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
Debug.WriteLine($"OnTextChanged:\"{tBox.Text}\"");
iCtrl?.Items.Refresh();
}
private void OnFilterCV(object sender, FilterEventArgs e)
{
e.Accepted = string.IsNullOrWhiteSpace(tBoxCV.Text) ||
((string)e.Item).Contains(tBoxCV.Text, StringComparison.OrdinalIgnoreCase);
}
private void OnTextChangedCV(object sender, TextChangedEventArgs e)
{
Debug.WriteLine($"OnTextChangedCV:\"{tBoxCV.Text}\"");
cvs?.View.Refresh();
}
}
Am I misunderstanding something or doing something wrong?
Updated.
Solution based on comment from #BionicCode.
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
Debug.WriteLine($"OnTextChanged:\"{tBox.Text}\"");
//iCtrl?.Items.Refresh();
if (iCtrl != null)
iCtrl.Items.Filter = new Predicate<object>(OnFilter);
}

The ItemsControl.Items is of type ItemsCollection. ItemsCollection implements a different Refresh behavior. The Items property is basically intended for internal use. If you have to rely on CollectionView.Refresh you should use the CollectionView explicitly:
ItemsControl itemsControl;
itemsControl.Items.Filter = item => (item as string).Contains("A");
CollectionView collectionView = CollectionViewSource.GetDefaultView(itemsControl.ItemsSource);
collectionView.Refresh();

Related

Attached Behavior handling an Attached Event in WPF

I googled regarding this question but couldn't gather any information and I was wondering if it is possible for an attached behavior to handle an attached event??
I've an event declared in a class and a behavior that I am attaching to a TextBox control, the event will be raised when a button is clicked. I added the handler for this event in my behavior and wrote the logic in the event handler, but it is not executed. So, I was wondering if it is possible for an attached behavior to handle an attached event or not?
class ResetInputEventClass
{
public static readonly RoutedEvent ResetInputEvent = EventManager.RegisterRoutedEvent("ResetInput",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ResetInputEventClass));
public static void AddResetInputEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.AddHandler(ResetInputEventClass.ResetInputEvent, handler);
}
public static void RemoveResetInputEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.RemoveHandler(ResetInputEventClass.ResetInputEvent, handler);
}
}
That is my Event class and this is how I am handling it in the behavior
public class MyBehavior : Behavior<TextBoxBase>
{
public MyBehavior()
{
// Insert code required on object creation below this point.
}
protected override void OnAttached()
{
base.OnAttached();
// Insert code that you would want run when the Behavior is attached to an object.
ResetInputEventClass.AddResetInputEventHandler(AssociatedObject, OnResetInputEvent);
}
protected override void OnDetaching()
{
base.OnDetaching();
// Insert code that you would want run when the Behavior is removed from an object.
ResetInputEventClass.RemoveResetInputEventHandler(AssociatedObject, OnResetInputEvent);
}
private void OnResetInputEvent(Object o, RoutedEventArgs e)
{
//Logic
}
}
Here is my XAML Code:
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBox Margin="5" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<TextBox Margin="5" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
and I am raising the event in the click event of my button
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
RaiseEvent(eventArgs);
}
Your problem is simple. The textbox is registered for the event, but the parent of the textbox is raising it. Thus the handler is never called. You can change the event to make it a Tunneling event instead of Bubbling. Or you can get a handle on your textbox (give it a name and reference in code behind). And have it raise the event.
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBox Margin="5" x:Name="byeTextBox" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
Your code-behind should then look like this
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
byeTextBox.RaiseEvent(eventArgs);
}
and that should fix your problem.
Of course it is possible. Show me your XAML and I ll tel you how an attached event triggers an attached behavior.
Edited:
I dont see the need why you using attached behavior and attached events because you could do everything in code behind.
Here is how to do everything in code behind:
Here is XAML without attached properties:
<Grid>
<StackPanel>
<TextBox x:Name="txtBox" Margin="5" Text="Bye" TextWrapping="Wrap" Width="150"/>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
This is code behind.
public MainWindow()
{
InitializeComponent();
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
this.txtBox.Text = "hello";
}
Because you have set Name property on TextBox and Button you can access them from code behind in your Window.cs and you can write your handler easly.
Here is how you can do everything with attached properties:
This is the new XAML for the solution with attached properties. I had to create my custom Interaction because the one you are using is Expression Blend or silverlight and not pure WPF.
<Grid x:Name="LayoutRoot">
<StackPanel i:Interaction.Behaviour="True">
<TextBox x:Name="txtBox" Margin="5" Text="Bye" TextWrapping="Wrap" Width="150"/>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
I had to set Behavior on True because the default value is false and when value is not equal to the old then the propery changed event will be called with my custom logic like this:
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
RaiseEvent(eventArgs);
}
public class Interaction : DependencyObject
{
// Using a DependencyProperty as the backing store for Behaviour. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BehaviourProperty =
DependencyProperty.RegisterAttached("Behaviour", typeof(bool), typeof(Interaction), new PropertyMetadata(false, new PropertyChangedCallback(OnBehaviourChanged)));
private static void OnBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StackPanel sp = (StackPanel)d;
sp.Dispatcher.BeginInvoke(new Action(() =>
{
TextBox tb = VisualTreeHelper.GetChild(sp, 0) as TextBox;
ResetInputEventClass.AddResetInputHandler(sp, new RoutedEventHandler((o, a) =>
{
// Do here whatever you want, call your custom expressions.
tb.Text = "hello";
}));
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
Inside property changed event which will be called as I already mentioned when I change false to true. I wait till everything is intialized by telling the dispatcher to execute my code when application is in background. Then I find the TextBox and inject the handler which will be called when you trigger ResetInput event.
This is very complicated solution but it will work with attached events and attached properties.
I highly recommend you to use the code behind for this scenario.
Also you made a mistake inside your ResetInputEventClass class. Add and Remove methods are not correctly spelled.
This is how you should have written them:
public static void AddResetInputHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.AddHandler(ResetInputEventClass.ResetInputEvent, handler);
}
public static void RemoveResetInputHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.RemoveHandler(ResetInputEventClass.ResetInputEvent, handler);
}
Have fun, I hope I helped you out.
You could also have achieved this with Commands

How can I execute a command binding on MouseEnter of a StackPanel in WPF?

I'm using MVVM.
<ItemsControl ItemsSource="{Binding AllIcons}" Tag="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label HorizontalAlignment="Right">x</Label>
<Image Source="{Binding Source}" Height="100" Width="100" />
<Label HorizontalAlignment="Center" Content="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
That looks fine. If I put a button in the stack panel using this command:
<Button Command="{Binding Path=DataContext.InvasionCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}"/>
I'm able to capture the command. However, I want to execute the command binding when the mouse enters the stack panel, not when I click a button.
Any idea?
My wrong, input bindings does not solve the problem. You may use attached properties for this:
public static class MouseEnterCommandBinding
{
public static readonly DependencyProperty MouseEnterCommandProperty = DependencyProperty.RegisterAttached(
"MouseEnterCommand",
typeof(ICommand),
typeof(MouseEnterCommandBinding),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetMouseEnterCommand(UIElement element, ICommand value)
{
element.SetValue(MouseEnterCommandProperty, value);
element.MouseEnter += (s,e) =>
{
var uiElement = s as UIElement;
var command = GetMouseEnterCommand(uiElement);
if (command != null && command.CanExecute(uiElement.CommandParameter))
command.Execute(uiElement.CommandParameter);
}
}
public static ICommand GetMouseEnterCommand(UIElement element)
{
return element.GetValue(MouseEnterCommandProperty) as ICommand;
}
}
First you need to declare a behavior for mouse enter. This basically translates the event into a command in your ViewModel.
public static class MouseEnterBehavior
{
public static readonly DependencyProperty MouseEnterProperty =
DependencyProperty.RegisterAttached("MouseEnter",
typeof(ICommand),
typeof(MouseEnterBehavior),
new PropertyMetadata(null, MouseEnterChanged));
public static ICommand GetMouseEnter(DependencyObject obj)
{
return (ICommand)obj.GetValue(MouseEnterProperty);
}
public static void SetMouseEnter(DependencyObject obj, ICommand value)
{
obj.SetValue(MouseEnterProperty, value);
}
private static void MouseEnterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = obj as UIElement;
if (uiElement != null)
uiElement.MouseEnter += new MouseEventHandler(uiElement_MouseEnter);
}
static void uiElement_MouseEnter(object sender, MouseEventArgs e)
{
UIElement uiElement = sender as UIElement;
if (uiElement != null)
{
ICommand command = GetMouseEnter(uiElement);
command.Execute(uiElement);
}
}
}
Then you just need to create that command in your view model and reference it in the view. The behaviors: namespace should just point to wherever you created that behavior. I use this pattern every time I need to translate an event into a command in a view model.
<Grid>
<StackPanel behaviors:MouseEnterBehavior.MouseEnter="{Binding MouseEnteredCommand}"
Height="150"
Width="150"
Background="Red">
</StackPanel>
</Grid>
You probably need to use InputBindings: http://msdn.microsoft.com/en-us/library/system.windows.input.inputbinding.aspx

Master Detail same View binding controls

say I have a ListView with an ItemControl. And a Details part that shows the selected Item from the ListView. Both are in the same xaml page. I tried everything to accomplish it, but what do I miss?
<!-- // List -->
<ItemsControl ItemsSource="{Binding Path=Model, ElementName=SomeListViewControl, Mode=Default}" SnapsToDevicePixels="True" Focusable="False" IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<SomeListView:SomeListItemControl x:Name=listItem/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- // Details -->
<Label Content="Begindatum" FontSize="16" VerticalAlignment="Center" Grid.Row="1" Margin="2,0,0,0"/>
<TextBox x:Name="Begindatum" Grid.Column="1" Grid.Row="1" Text="{Binding Path=BeginDate, ElementName=listItem,Converter={StaticResource DateTimeConverter}, ConverterParameter=dd-MM-yyyy}" IsEnabled="False" Style="{DynamicResource TextBoxStyle}" MaxLength="30"/>
public event EventHandler<DataEventArgs<SomeEntity>> OnOpenSomething;
public ObservableCollection<SomeEntity> Model
{
get { return (ObservableCollection<SomeEntity>)GetValue(ModelProperty); }
set
{
Model.CollectionChanged -= new NotifyCollectionChangedEventHandler(Model_CollectionChanged);
SetValue(ModelProperty, value);
Model.CollectionChanged += new NotifyCollectionChangedEventHandler(Model_CollectionChanged);
UpdateVisualState();
}
}
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(ObservableCollection<SomeEntity>), typeof(SomeListView), new UIPropertyMetadata(new ObservableCollection<SomeEntity>(), new PropertyChangedCallback(ChangedModel)));
private static void ChangedModel(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
SomeListView someListView = source as SomeListView;
if (someListView.Model == null)
{
return;
}
CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(someListView.Model);
}
private void Model_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (Model == null)
{
return;
}
}
Do not use an ItemsControl - ItemsControl does not have a SelectedItem property - and therefore you cannot determine which one is selected.
Use a ListBox instead and then in the detail section make a binding like so: ... DataContext="{Binding SelectedItem,ElementName=ListboxName}" ... where ListboxName is the Name property of the ListBox you use.

Create Event Handler for TreeViewItem in WPF

Im adding items to TreeView control via ItemsSource property and ItemTemplate property to set the template for TreeViewItem. How can i add an event handler to handle selection change event on TreeViewItems?
For now my ItemTemplate looks like this:
<Window.Resources><DataTemplate x:Key="PeerDetailTemplate">
<TextBlock Text="{Binding DESCRIPTION}" Tag="{Binding ID}" GotFocus="GetModules"/>
</DataTemplate></Window.Resources>
But it doesnt work (GetModules is not called). Im new to WPF, so show me the right direction to do such things, please.
If you want to capture the SelectedItemChanged event in a TreeView, then you need to set the event handler on the parent node, i.e.,
XAML
<StackPanel>
<TreeView SelectedItemChanged="OnTreeViewSelectedItemChanged">
<TreeViewItem Header="Desktop">
<TreeViewItem Header="Computer" />
<TreeViewItem Header="My Documents" />
<TreeViewItem Header="c:\" />
</TreeViewItem>
<TreeViewItem Header="Recyle Bin" >
<TreeViewItem Header="foo.txt" />
<TreeViewItem Header="bar.txt" />
<TreeViewItem Header="fizz.buzz" />
</TreeViewItem>
<TreeViewItem Header="Control Panel" >
<TreeViewItem Header="Programs" />
<TreeViewItem Header="Security" />
<TreeViewItem Header="User Accounts" />
</TreeViewItem>
</TreeView>
<TextBlock Margin="20" x:Name="MyTextBlock" />
</StackPanel>
Code Behind:
private void OnTreeViewSelectedItemChanged( object sender, RoutedPropertyChangedEventArgs<object> e )
{
MyTextBlock.Text = ( (TreeViewItem) ( (TreeView) sender ).SelectedItem ).Header.ToString();
}
You'll need to add an event handler to the TreeView's SelectedItemChanged event.
<TreeView x:Name="myTreeView"
SelectedItemChanged="myTreeView_SelectedItemChanged"
ItemTemplate="{StaticResource PeerDetailTemplate} />
Since this is fired after the selection is changed, you can use the TreeView's selected item property to access the tree view item:
private void myTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem selectedItem = (TreeViewItem)myTreeView.SelectedItem;
// do stuff
}
Selection and Selection and focus are two different concepts. It sounds like you're interested in selection, which in this case is a property of the TreeView. Event TreeView.SelectedItemChanged will notify you of selection changes and property TreeView.SelectedItem will tell you what is selected.
There are different ways to Bind of the SelectedItemChanged event of TreeviewItem:
Method 1: Direct Attaching Event
The attachment of the event can be done in Xaml
<TreeView x:Name="treeview1" HorizontalAlignment="Left" Height="243" Margin="30,211,0,0" VerticalAlignment="Top" Width="667" SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeViewItem Header="TreeViewItem"/>
</TreeView>
Private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
}
Or on code Behind
myTreeview.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeView_SelectedItemChanged);
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
}
Method 2 Usage of Extended Class
public class ExtendedTreeView : TreeView
{
public ExtendedTreeView()
: base()
{
this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeView_SelectedItemChanged);
}
void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (SelectedItem != null)
{
SetValue(SelectedItem_Property, SelectedItem);
}
}
public object SelectedItem_
{
get { return (object)GetValue(SelectedItem_Property); }
set { SetValue(SelectedItem_Property, value); }
}
public static readonly DependencyProperty SelectedItem_Property = DependencyProperty.Register("SelectedItem_", typeof(object), typeof(ExtendedTreeView), new UIPropertyMetadata(null));
}
Next just create the Treeview with the new custom class object.
ExtendedTreeView myTreeview = new ExtendedTreeView();
Method 3 Usage of Behavior
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
Next just attach the Treeview to the Behavior
On Xaml file
<TreeView>
<e:Interaction.Behaviors>
<behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</e:Interaction.Behaviors>
</TreeView>
Or on Code Behind
BindableSelectedItemBehavior selectedItemBehavior = new BindableSelectedItemBehavior();
System.Windows.Interactivity.Interaction.GetBehaviors(treeview1).Add(selectedItemBehavior);
Cordially

Element Binding Silverlight 3

I trying to use Element Binding in Silverlight 3 to SelectedItem of ComboBox in ToolTipService.ToolTip.
This code works:
<ComboBox x:Name="cboSource" DisplayMemberPath="Name" ToolTipService.ToolTip="{Binding ElementName=cboSource, Path=SelectedItem.Name}" Width="180" />
but this code doesn't:
<ComboBox x:Name="cboSource" DisplayMemberPath="Name" Width="180" >
<ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ElementName=cboSource, Path=SelectedItem.Code}" Margin="0,0,5,0"/>
<TextBlock Text="-" Margin="0,0,5,0"/>
<TextBlock Text="{Binding ElementName=cboSource, Path=SelectedItem.Name}"/>
</StackPanel>
</ToolTipService.ToolTip>
</ComboBox>
Name and Code are properties of item in cboSource.ItemsSource.
In first code, the Name is correctly displayed in combo's tooltip but in second code tooltip is " - ".
Any ideas ?
Ahh...fun with tooltips.
The ToolTipService is actually "rooted" at the base of the tree (if you have Mole, you can double check to verify this) - hence, it does not get it's DataContext propagated down from parent elements.
I've done hacky things to fix this behavior in the past, but they all boil down to "Code up an attached property that accepts a DataContext and forwards it along to the attached element".
Best of luck - this thing has stung me a couple of times. :)
Ooh, found a link for you: http://www.codeproject.com/Articles/36078/Silverlight-2-0-How-to-use-a-DataBinding-with-the-ToolTipService.aspx
EDIT: Try this out:
<ComboBox x:Name="cboSource" DisplayMemberPath="Name" Width="180">
<local:DataBindingTooltip.TooltipDataContext>
<Binding ElementName="cboSource"/>
</local:DataBindingTooltip.TooltipDataContext>
<local:DataBindingTooltip.Tooltip>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=SelectedItem.Code}" Margin="0,0,5,0"/>
<TextBlock Text="-" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Path=SelectedItem.Name}"/>
</StackPanel>
</local:DataBindingTooltip.Tooltip>
</ComboBox>
With the following class:
public class DataBindingTooltip
{
public static readonly DependencyProperty TooltipDataContextProperty =
DependencyProperty.RegisterAttached(
"TooltipDataContext",
typeof (object),
typeof (DataBindingTooltip),
null);
public static readonly DependencyProperty TooltipProperty =
DependencyProperty.RegisterAttached(
"Tooltip",
typeof(object),
typeof(DataBindingTooltip),
new PropertyMetadata(TooltipChanged));
public static void SetTooltip(DependencyObject d, object value)
{
d.SetValue(TooltipProperty, value);
}
public static object GetTooltip(DependencyObject d)
{
return d.GetValue(TooltipProperty);
}
public static void SetTooltipDataContext(DependencyObject d, object value)
{
d.SetValue(TooltipDataContextProperty, value);
}
public static object GetTooltipDataContext(DependencyObject d)
{
return d.GetValue(TooltipDataContextProperty);
}
private static void TooltipChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement)
{
var element = sender as FrameworkElement;
element.Loaded += ElementLoaded;
}
}
static void ElementLoaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement)
{
var element = sender as FrameworkElement;
element.Loaded -= ElementLoaded;
var tooltip = element.GetValue(TooltipProperty) as DependencyObject;
if (tooltip != null)
{
if (GetTooltipDataContext(element) != null)
{
tooltip.SetValue(FrameworkElement.DataContextProperty,
element.GetValue(TooltipDataContextProperty));
}
else
{
tooltip.SetValue(FrameworkElement.DataContextProperty,
element.GetValue(FrameworkElement.DataContextProperty));
}
}
ToolTipService.SetToolTip(element, tooltip);
}
}
}
A very simple way could be to define an additional property in the source object something
whenever a user hovers the mouse over the control, the concatenated string will be shown as a nice simple tooltip.
like this:
using System...
....
public Class Employee
{
public string Forenames {get;set;}
public string Surname {get;set;}
public string Address {get;set;}
private string tooltip;
public string Tooltip
{
get{return tooltip;}
set
{
value=Forenames + " " + Surname + "," Address ;
}
}
//... other methods to follow
}
XAML MyPage.cs code has following
public partial Class MyPage : Page
{
Public List<Employee> Employees{get;set;}
public MyPage()
{
InitiazeComponents();
Employees = new List<Employee>(); // initialise
Employees=GetEmployees();
}
public List<Employee> GetEmployees(){
..
Write code that ..returns
..
}
.. other code to follow..
}
Now in MyPage.xaml
...
<ComboBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="8,4,0,0" Name="cboCostCentreInvestor" ItemsSource="{Binding Employees}" ToolTipService.ToolTip="{Binding ElementName=cboCostCentreInvestor,Path=SelectedItem.Tooltip}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Style="{StaticResource stackPanelComboboxItemStyle}">
<TextBlock Text="{Binding Forenames}" Style="{StaticResource textBlockComboboxItem}" />
<TextBlock Text="{Binding Surname}" Style="{StaticResource textBlockComboboxItem}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Resources