Bind to ListBox selectedItem inside TabControl - wpf

Okay, I got this Tabcontrol containing a ListBox. Now my problem it that I would like to bind <TextBox x:Name="DetailTextBox" Text="{Binding Detail}"/> to the selectedItem in the listbox and show the Detail property value.
Note that the TextBox is not part of the TabControl, but is in another Column.
I can't quite figure out, how to handle binding, when there a multiple ListBox'es, one in each TabControl Item.
My classes
public class TabViewModel
{
public string Name { get; set; }
public ObservableCollection<TabItemViewModel> Collection { get; set; }
}
public class TabItemViewModel
{
public string Title { get; set; }
public string Detail { get; set; }
}
public MainWindow()
var tabViewModels = new ObservableCollection<TabViewModel>();
tabViewModels.Add(new TabViewModel{Name = "Tab 1", Collection = new ObservableCollection<TabItemViewModel>{new TabItemViewModel{Detail = "Detail 1.1", Title = "Title 1.1"}, new TabItemViewModel{Detail = "Detail 2.2", Title = "Title 2.2"}}});
tabViewModels.Add(new TabViewModel { Name = "Tab 2", Collection = new ObservableCollection<TabItemViewModel> { new TabItemViewModel { Detail = "Detail 2.1", Title = "Title 2.1" }, new TabItemViewModel { Detail = "Detail 2.2", Title = "Title 2.2" } } });
tabViewModels.Add(new TabViewModel { Name = "Tab 3", Collection = new ObservableCollection<TabItemViewModel> { new TabItemViewModel { Detail = "Detail 3.1", Title = "Title 3.1" }, new TabItemViewModel { Detail = "Detail 3.2", Title = "Title 3.2" } } });
DataContext = tabViewModels;
MainWindow.xaml.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<TabControl ItemsSource="{Binding}" Grid.Column="0" SelectedIndex="0">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header">
<Setter.Value>
<Binding Path="Name"/>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Collection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<StackPanel Grid.Column="1">
<TextBox x:Name="DetailTextBox" Text="{Binding Detail}"/>
</StackPanel>
</Grid>
EDIT
Temp Solution
Found a way to make it work, but I'm still looking for a pure Xaml solution.
Added a SelectionChange event
<ListBox ItemsSource="{Binding Collection}" SelectionChanged="ListBox_SelectionChanged">
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
DetailTextBox.DataContext = (TabItemViewModel) e.AddedItems[0];
}

How about this, I was surprised myself :-)
Make these changes to your Xaml.
<TabControl ItemsSource="{Binding}"
Grid.Column="0" SelectedIndex="0"
IsSynchronizedWithCurrentItem="True">
<ListBox ItemsSource="{Binding Collection}"
IsSynchronizedWithCurrentItem="True">
<TextBox x:Name="DetailTextBox"
Text="{Binding /Collection/Detail}"/>
The '/' binds to the currently selected item of a control's CollectionView.
So the binding above is drilling down through
The currently SelectedItem of the ObservableCollection set on the Data Context
The Collection property on that item
The currently SelectedItem of the Collection property (ObservableCollection)
The Detail property on that item.
In order for this to work we need to specify IsSynchronizedWithCurrentItem="True" to ensure the SelectedItem remains synchronized with the current item of each collection.

Related

prism 5.0 InvokeCommandAction SelectionMode="Multiple"

I want to detect all selected items in List Box through InvokeCommandAction prism 5.0.
XAML:
<Window x:Class="Selection.Prism5._0.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:Selection.Prism5"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
Title="MainWindow"
Height="350"
Width="525">
<FrameworkElement.DataContext>
<local:MainViewModel />
</FrameworkElement.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectItemsCommand}"
TriggerParameterPath="AddedItems" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
View Model:
public class MainViewModel : BindableBase
{
public MainViewModel()
{
this.Items = new List<Model>
{
new Model {Id=1,Name="Name 1" },
new Model {Id=2,Name="Name 2" },
new Model {Id=3,Name="Name 3" },
new Model {Id=4,Name="Name 4" },
new Model {Id=5,Name="Name 5" },
new Model {Id=6,Name="Name 6" }
};
SelectItemsCommand = new DelegateCommand<object[]>((items) =>
{
if (items != null && items.Count() > 0)
{
SelectedItems = items.Select(i => (Model)i);
}
});
}
public ICommand SelectItemsCommand { get; private set; }
private IEnumerable<Model> _items;
public IEnumerable<Model> Items
{
get { return _items; }
set
{
_items = value; base.OnPropertyChanged("Items");
}
}
private IEnumerable<Model> _selectedItems;
public IEnumerable<Model> SelectedItems
{
get { return _selectedItems; }
set
{
_selectedItems = value; base.OnPropertyChanged("SelectedItems");
}
}
private Model _selectedItem;
public Model SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value; base.OnPropertyChanged("SelectedItem");
}
}
}
It should work, however it doesn't. When I select more than one item in GUI:
View model code recognizes only one item:
I saw this example in "What's New in Prism 5.0" by Brian Lagunas and as far as I understood, this technic may be used for multi-selection too.
What have I done wrong here?
SelectionChangedEventArgs.AddedItems gives you the selection list of a particular action. if ListBox SelectionMode is "Multiple" or "Single" you cannot select multiple items at a single click. if ListBox SelectionMode is "Extended" you can select multiple items with the help of the shift key.
SelectionChangedEventArgs.AddedItems will not give all selected items of the list box for a particular action.
For your need,
In View,
Change TriggerParameterPath="AddedItems" to TriggerParameterPath="Source.SelectedItems" .
In ViewModel
Change DelegateCommand<object[]> to DelegateCommand<System.Collections.ICollection>
Ref:
http://frststart.blogspot.com/2016/10/selectionchangedselecteditemstutorial.html
You are selecting one item at a time and the command is invoked for each time you select an item.
This is the expected behaviour.
If you want to keep track of the currently selected items you could add an IsSelected property to the Model class and use an ItemContainerStyle to bind this one to the IsSelected property of the ListBoxItem container:
<ListBox SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding SelectItemsCommand}"
TriggerParameterPath="AddedItems" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
SelectItemsCommand = new DelegateCommand<System.Collections.IList>((items) =>
{
SelectedItems = Items.Where(x => x.IsSelected).ToList();
});

ItemsControl (WrapPanel) Grouping should split GroupItems

I have a ItemsControl with a WrapPanel as ItemsHost and multiple Groupings.
Got this going so far with the following Templates:
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" x:Name="PART_Header" Content="{TemplateBinding Content}" />
<ItemsPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" Orientation="Vertical" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
Now I have the Problem that every Group does start a new Column why I want it to Continue right under the last GroupItem and Wrap in the Middle of the GroupItem instead of at the beginning.
It should look like the Windows 8 Apps overview (not the start page, if you go down to the overview)
Is that possible?
I solved this in the ViewModel instead. I add a GroupItem into the ObservableCollection that is styled like a (Expandable) GroupHeader.
Than I added a seperate DataTemplate for the GroupHeader that sets a IsCollapsed property on the Group. All Items do now have a reference to the parent Group and bind the Visibility to the IsCollapsed property of the Parent Group.
Sadly i was not able to achive this using the CollectionViewSource.
This is the XAML:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.DataContext>
<local:ViewModel/>
</ItemsControl.DataContext>
<ItemsControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<DataTemplate DataType="{x:Type local:GroupViewModel}">
<StackPanel>
<CheckBox IsChecked="{Binding IsExtended}" />
<!--Restyle to look like a extender-->
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ItemViewModel}">
<TextBlock Text="{Binding Name}"
Visibility="{Binding Group.IsExtended, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
This is the ViewModel:
public class ViewModel
{
public ViewModel()
{
//Some Test data
GroupViewModel group1 = new GroupViewModel("Group1");
GroupViewModel group2 = new GroupViewModel("Group2");
this.Items = new ObservableCollection<object>(new[]
{
new ItemViewModel("Item1", group1),
new ItemViewModel("Item2", group1),
new ItemViewModel("Item3", group2),
new ItemViewModel("Item4", group2)
});
string groupName = string.Empty;
foreach (ItemViewModel item in this.Items.ToArray())
{
//Insert Group headers
if (item.Group.Name != groupName)
{
groupName = item.Group.Name;
this.Items.Insert(this.Items.IndexOf(item), item.Group);
}
}
}
public ObservableCollection<object> Items { get; }
}
public class GroupViewModel : ViewModelBase
{
private bool isExtended = true;
public GroupViewModel(string name)
{
this.Name = name;
}
public string Name { get; }
public bool IsExtended
{
get { return this.isExtended; }
set { this.SetProperty(ref this.isExtended, value); }
}
}
public class ItemViewModel
{
public ItemViewModel(string name, GroupViewModel group)
{
this.Name = name;
this.Group = group;
}
public string Name { get; }
public GroupViewModel Group { get; }
}
You can't do that kind of wrapping directly, since groups are in one panel and items in the each group in their own panel.
You have two options:
Use combination of viewmodel and Datatemplates to simulate grouping. ItemsSource won't be grouped CollectionView, just plain collection, but the items will contain group name. In DataTemplate you will show Group Header for the first item in each group.
Create your own control, which will arrange children into groups out of the box. Quite a lof of work, but much better reusability. I believe this is how WinRT GridView and ListView grouping works. Maybe you can find simmilar 3rd party controls in WPF

Reusable usercontrol with radiobuttons are being unchecked unexpectedly

I'm having an issue with the RadioButton when using a reusable UserControl. For some reason I am unable to check a radio button of each 'Chooser' control but instead when one radio button is checked, all other radio buttons in the current chooser and in the other choosers are unchecked. Does anyone know how to change this code so that I can have a checked item in each 'chooser' user control. The user control must be able to be dynamically built using a collection. In a real world example, each 'chooser' usercontrol will have different text values.
MainPage.xaml
<StackPanel x:Name="LayoutRoot" Background="White">
<radioButtonTest:Chooser />
<radioButtonTest:Chooser />
<radioButtonTest:Chooser />
</StackPanel>
Chooser.xaml
<UserControl x:Class="RadioButtonTest.Chooser"
xmlns ...>
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal">
<TextBlock x:Name="MyLabel" Text="Choices:" VerticalAlignment="Center" Margin="0,0,10,0" />
<ItemsControl x:Name="MyChooser" Height="25" VerticalAlignment="Top" HorizontalAlignment="Left">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Height="22" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="{Binding}" MinWidth="35" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
Chooser.xaml.cs
public partial class Chooser
{
public Chooser()
{
InitializeComponent();
// adding some test items to the itemscontrol
MyChooser.ItemsSource = new ObservableCollection<string>
{
"first",
"second",
"third"
};
}
}
Turns out I needed to use the GroupName property of the RadioButton to dictate the groupings. I did this by changing the item source to a custom class with a Group property of type string and binding this property to the GroupName property on the RadioButton.
XAML:
<DataTemplate>
<RadioButton ... Content="{Binding Name}" GroupName="{Binding Group}" />
</DataTemplate>
C#:
public Chooser()
{
InitializeComponent();
// the groupName needs to be the same for each item
// in the radio group, but unique for each separate group.
var groupName = Guid.NewGuid().ToString();
MyChooser.ItemsSource = new ObservableCollection<RadioButtonGroupItem>
{
new RadioButtonGroupItem {Group = groupName, Name = "first"},
new RadioButtonGroupItem {Group = groupName, Name = "second"},
new RadioButtonGroupItem {Group = groupName, Name = "third"}
};
}
public class RadioButtonGroupItem
{
public string Name { get; set; }
public string Group { get; set; }
}
Hope this helps someone.

Silverlight the first combobox filter the second combobox

I have two comboboxes created as common controls. When the page is loaded, the programcbo lists the programs names; and the teamcbo list the team names. I want to ONLY display the related team names when any program name has been selected. In a word, I need to filter the second combobox by selecting the name from the first combobox.
Thanks in advance.
There are a variety of ways you could handle this.
If you have, for example, "Program" objects that contain collections of "Teams", you could do it like this, almost all in XAML:
<StackPanel>
<ComboBox x:Name="programCbo" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProgramName}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="teamCbo" ItemsSource="{Binding ElementName=programCbo, Path=SelectedItem.Teams}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TeamName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
You can see here that I've bound the first combobox to the datacontext, which is a list of Programs (we'll set that in the next bit). The second combobox is set to the selecteditem property of the first combobox, and then the Teams property on that. This way, when the selection changes on the first combobox, databinding kicks in and causes the itemssource on the second box to update.
In the code behind, I just build up the datasource. Obviously you'd have to get your data your own way:
public MainPage()
{
InitializeComponent();
DataContext = new List<Program>
{
new Program
{
ProgramName = "Program 1",
Teams = new List<Team>
{
new Team
{
TeamName = "Program 1 Team 1"
},
new Team
{
TeamName = "Program 1 Team 2"
}
}
},
new Program
{
ProgramName = "Program 2",
Teams = new List<Team>
{
new Team
{
TeamName = "Program 2 Team 1"
},
new Team
{
TeamName = "Program 2 Team 2"
}
}
}
};
}
If you don't have your data in a way that's accessible like this, you'll have to handle the Selection changed event on your combobox:
(XAML)
<StackPanel>
<ComboBox x:Name="programCbo" SelectionChanged="programCbo_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProgramName}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox x:Name="teamCbo">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TeamName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
Notice this time, we've set a handler for the SelectionChanged event.
(CodeBehind)
public MainPage()
{
InitializeComponent();
programCbo.ItemsSource = new List<Program>
{
new Program
{
ProgramName = "Program 1",
},
new Program
{
ProgramName = "Program 2",
}
};
}
private void programCbo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// get the sender
ComboBox cb = sender as ComboBox;
// get the selected program
Program selectedProgram = (cb.SelectedItem as Program);
// do some stuff to get the appropriate teams and set the other combobox's itemssource to it
teamCbo.ItemsSource = new List<Team>
{
new Team
{
TeamName = "My favorite team!"
}
};
}
And there you have it. Long winded, but hopefully thorough with examples :)
You can use a paged collection to filter or you can modify your observable collection which is bound to the second combobox when the value that's bound to the first combobox raises on prop change.
xaml:
...
xmlns:local="clr-namespace:SelectedProgram">
<UserControl.Resources>
<local:MainPageViewModel x:Key="vm" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource vm}}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="350" />
</Grid.RowDefinitions>
<sdk:Label Content="Select Team for the Program" Grid.Row="0" Margin="20,0,0,0"/>
<StackPanel Orientation="Horizontal" Grid.Row="1" VerticalAlignment="Top" Margin="20,0,0,0">
<ComboBox x:Name="programcbo" Width="100" Height="25"
ItemsSource="{Binding Path=Programs, Mode=OneWay}"
SelectedValue="{Binding Path=SelectedTeam, Mode=TwoWay}"
DisplayMemberPath="Key"
SelectedValuePath="Value" />
<ComboBox x:Name="teamcbo" Width="100" Height="25" Margin="20"
ItemsSource="{Binding Path=SelectedTeam, Mode=OneWay}"/>
</StackPanel>
</Grid>
In class like MainPageViewModel:
...
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Windows.Data;
namespace SelectedProgram
{
public class MainPageViewModel : ModelBase
{
public MainPageViewModel()
{
this.Programs = GetPrograms();
}
private string[] _selectedTeam;
public string[] SelectedTeam
{
get { return _selectedTeam; }
set
{
_selectedTeam = value;
FirePropertyChanged("SelectedTeam");
}
}
private ObservableCollection<KeyValuePair<string, string[]>> _programs;
public ObservableCollection<KeyValuePair<string, string[]>> Programs
{
get { return _programs; }
set
{
_programs = value;
FirePropertyChanged("Programs");
}
}
private ObservableCollection<KeyValuePair<string, string[]>> GetPrograms()
{
ObservableCollection<KeyValuePair<string, string[]>> pr = new ObservableCollection<KeyValuePair<string, string[]>>();
pr.Add(new KeyValuePair<string, string[]>("Janitors", new string[]{ "Schwarzkopf", "Ivanov", "Smith"}));
pr.Add(new KeyValuePair<string, string[]>("Violonists", new string[] { "Einstein", "Odinzovobulyznicof", "Onestone" }));
pr.Add(new KeyValuePair<string, string[]>("Programers", new string[] { "Petrov", "Skeet", "Stroustrup" })); return pr;
}
}
}

WPF Show data from multiple DataContexts in ToolTip of ItemsControl

I am trying to display a tooltip for an item generated by an ItemsControl that needs to pull data from conceptually unrelated sources. For example, say I have an Item class as follows:
public class Item
{
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
I can display the Item within an ItemsControl with a tooltip as follows:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<TextBlock Text="{Binding ItemDescription}" />
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But say I have another property that can be accessed via the DataContext of the ItemsControl. Is there any way to do this from within the tooltip? E.g.,
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" Text="{Bind this to another property of the ItemsControl DataContext}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The code for the test Window I used is as follows:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>() {
new Item() { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item() { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
So in this example I want to show the value of the GlobalText property (in reality this would be another custom object).
To complicate matters, I am actually using DataTemplates and show two different types of objects within the ItemsControl, but any assistance would be greatly appreciated!
After an hour of hair pulling I have come to the conviction that you can't reference another DataContext inside a DataTemplate for a ToolTip. For other Bindings it is perfectly possible as other posters have proven. That's why you can't use the RelativeSource trick either. What you can do is implement a static property on your Item class and reference that:
<Window x:Class="ToolTipSpike.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
Name="Root"
xmlns:ToolTipSpike="clr-namespace:ToolTipSpike">
<Grid>
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1"
Text="{Binding Source={x:Static ToolTipSpike:Item.GlobalText},
Path=.}"
/>
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
using System.Collections.Generic;
using System.Windows;
namespace ToolTipSpike
{
public partial class Window1 : Window
{
public List<Item> Items { get; private set; }
public Window1()
{
InitializeComponent();
var itemList = new List<Item>
{
new Item { ItemName = "First Item", ItemDescription = "This is the first item." },
new Item { ItemName = "Second Item", ItemDescription = "This is the second item." }
};
this.Items = itemList;
this.DataContext = this;
}
}
public class Item
{
static Item()
{
GlobalText = "Additional Text";
}
public static string GlobalText { get; set; }
public string ItemName{ get; set;}
public string ItemDescription{ get; set;}
}
}
Second attempt
Ok, the Relative Source Binding doesn't work in this case. It actually works from a data template, you can find many examples of this on the Internets. But here (you were right, David, in your comment) ToolTip is a special beast that is not placed correctly in the VisualTree (it's a property, not a control per se) and it doesn't have access to the proper name scope to use relative binding.
After some more searching I found this article, which describes this effect in details and proposes an implementation of a BindableToolTip.
It might be an overkill, because you have other options -- like using a static property on a class (as in Dabblernl's response) or adding a new instance property to your Item.
First attempt :)
You should consult with the Relative Source Binding types (in this cheat sheet for example):
So your binding will look somehow similar to this:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path= GlobalText}
Almost correct Yacoder, and guessed way wrong there Dabblernl ;)
Your way of thinking is correct and it is possible to reference the DataContext of your ItemsControl
You are missing the DataContext property in path:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.GlobalText}
Second attempt ;)
http://blogs.msdn.com/tom_mathews/archive/2006/11/06/binding-a-tooltip-in-xaml.aspx
Here is an article with the same problem. They can reference the DataContext of their Parent control by the PlacementTarget property:
<ToolTip DataContext=”{Binding RelativeSource={RelativeSource Self},Path=PlacementTarget.Parent}”>
If you would place the DataContext on a deeper level, you avoid changing your Item DataContext
A second suggestion (Neil and Adam Smith) was that we could use PlacementTarget in the binding. This is nice, as I am actually inheriting the DataContext already from the page that hosts the DataControl, and this would allow the ToolTip to gain access back to the origial control. As Adam noted, though, you have to be aware of the parent/child structure off your markup:
This is a case where I think it's conceptually more appropriate to do this in the view model than it is in the view anyway. Expose the tooltip information to the view as a property of the view model item. That lets the view do what it's good at (presenting properties of the item) and the view model do what it's good at (deciding what information should be presented).
I had a very similar problem and arrived at this question seeking answers. In the end I came up with a different solution that worked in my case and may be useful to others.
In my solution, I added a property to the child item that references the parent model, and populated it when the children were generated. In the XAML for the ToolTip, I then simply referenced the property from the parent model on each element and set the DataContext to the parent model property.
I felt more comfortable with this solution than creating proxy elements in XAML and referencing them.
Using the example code for this question, you would do the following. Note I have not tested this scenario in a compiler, but have done so successfully implemented this solution in the code for my own scenario.
Item:
public class Item
{
public List<Item> Parent { get; set; }
public string ItemDescription { get; set; }
public string ItemName { get; set; }
}
Window:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Item> itemList = new List<Item>();
itemList.Add(new Item() { Parent = this, ItemName = "First Item", ItemDescription = "This is the first item." });
itemList.Add(new Item() { Parent = this, ItemName = "Second Item", ItemDescription = "This is the second item." });
this.Items = itemList;
this.GlobalText = "Something else for the tooltip.";
this.DataContext = this;
}
public string GlobalText { get; private set; }
public List<Item> Items { get; private set; }
}
XAML:
<ItemsControl x:Name="itemsControl" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}">
<TextBlock.ToolTip>
<ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding ItemDescription}" />
<TextBlock Grid.Row="1" DataContext={Binding Parent} Text="{Bind this to aproperty of the parent data model}" />
</Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Resources