Binding usercontrol inside itemcontrol - wpf

I have 2 tasks.
Add a single usercontrol to a parent window.
Add a collection of a usercontrol to a parent window.
I have problem to fulfill task 2 in relation to the data binding and command binding.
if someone knows how to do task 2, please add some code.
This is my implementation for both tasks, in case someone want to fix it.. :
I have a usercontrol called "Book" that contains 3 textblocks and a button.
The userControl has dependecyProperty of my book model and for the button command.
Book.xaml
<UserControl x:Name="MyBookControl"
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Controls:BookControl}}, Path=TheBook}">
<Label Grid.Row="0">Title</Label>
<Label Grid.Row="1">Author</Label>
<Label Grid.Row="2">Description</Label>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Title}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Author}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Description}"/>
<Button Grid.Row="3" Command="{Binding
SomeCommand,ElementName=MyBookControl}" Content="Save" />
</Grid>
Book.xaml.cs
public partial class BookControl : UserControl
{
public BookControl()
{
InitializeComponent();
}
public BookModel TheBook
{
get { return (BookModel)GetValue(TheBookProperty); }
set { SetValue(TheBookProperty, value); }
}
public static DependencyProperty TheBookProperty = DependencyProperty.Register("TheBook", typeof(BookModel), typeof(BookControl));
public ICommand SomeCommand
{
get { return (ICommand)GetValue(SomeCommandProperty); }
set { SetValue(SomeCommandProperty, value); }
}
public static readonly DependencyProperty SomeCommandProperty =
DependencyProperty.Register("SomeCommand", typeof(ICommand), typeof(BookControl), new UIPropertyMetadata(null));
}
BookModel.cs
public class BookModel
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
}
In order to complete task 1 I created a window:
BookWindow
<Window
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
>
<StackPanel>
<Controls:BookControl TheBook="{Binding Book}" SomeCommand="{Binding
SaveCommand}" />
</StackPanel>
BookViewModel.cs
public BookModel Book { get; set; }
public MainViewModel()
{
Book = new BookModel{Title = "A Book", Author = "Some Author",
Description = "Its a really good book!"};
}
private ActionCommand _SaveCommand;
public ICommand SaveCommand
{
get
{
if (_SaveCommand == null)
{
_SaveCommand = new ActionCommand(OnSaveCommand, CanSaveCommand);
}
return _SaveCommand;
}
}
protected virtual void OnSaveCommand()
{
MessageBox.Show("save clicked");
}
protected virtual bool CanSaveCommand()
{
return true;
}
Great, Task 1 Completed
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!116&authkey=!AHiyrfEnBr2a-rM&v=3&ithint=photo%2cpng
Now, trying to complete task 2:
ContainerWindow:
<Window
DataContext="{Binding Source={StaticResource Locator}, Path=Container}"
>
<StackPanel>
<ItemsControl ItemsSource="{Binding Books}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Controls:BookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
ContainerViewModel.cs :
private ObservableCollection<BookModel> books;
public ObservableCollection<BookModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookModel>();
}
return books;
}
}
public ContainerViewModel()
{
BookModel book1 = new BookModel { Title = "A Book 2", Author = "Some Author", Description = "Its a really good book!" };
BookModel book2 = new BookModel { Title = "A Book 3", Author = "Some Author", Description = "Its a really good book!" };
Books.Add(book1);
Books.Add(book2);
}
The Binding fail, the button "save" stops respoding.
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!121&authkey=!AKnyQk6Ge_9QHug&v=3&ithint=photo%2cpng
So, what is going on ? why binding fail, why the button "save" is not functioning ?

You're not setting your DependencyProperties in the list example.
<DataTemplate>
<Controls:BookControl />
</DataTemplate>
Look at how you did it in your non-list version.
<Controls:BookControl TheBook="{Binding Book}" SomeCommand="{Binding
SaveCommand}" />
That being said, you don't need the DependencyProperties at all, the UserControl will inherit the DataContext for each 'Book' in the list of books as the ItemsControl creates them. You just need to not set the DataContext on the grid.
Then your button could just bind to the BookViewModel command property.
<Button Grid.Row="3" Command="{Binding SaveCommand}" Content="Save" />
If your concern is not knowing what is available for the inherited DataContext, you could do this to get design time support.
d:DataContext="{d:DesignInstance Type=local:BookViewModel,
IsDesignTimeCreatable=False}"
Just make sure that the following is defined somewhere in the file, it usually is by default.
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Update
So I missed the second issue, should have actually fired up Visual Studio. The issue is that your command is in the MainViewModel.cs. That said, our UserControl has inherited the DataContext of each Book object. The short of it is that the button is looking for the command inside of the Book object.
I'm going to assume that since you have a save command that you will be editing the Book object. So let's take this chance to go ahead and make a ViewModel. I'm going to move the save command to there, so that save is always available off of a BookViewModel. There could be good reasons to have the save command somewhere else, but for simplicity's sake, we'll put it in the ViewModel.
Also, I'm not sure if you have INotifyPropertyChanged implemented anywhere, as your MainViewModel and ContainerViewModel don't show that one is used. If you don't, I'd highly recommend you take a step back and look into an implementation or an MVVM framework for your ViewModels.
BookViewModel.cs
public class BookViewModel
{
private readonly BookModel book;
public BookViewModel(BookModel book)
{
this.book = book;
SaveCommand = new ActionCommand(OnSaveCommand, CanSaveCommand);
}
public ICommand SaveCommand { get; private set; }
public string Title
{
get { return book.Title; }
set { book.Title = value; }
}
public string Author
{
get { return book.Author; }
set { book.Author = value; }
}
public string Description
{
get { return book.Description; }
set { book.Description = value; }
}
protected virtual void OnSaveCommand()
{
MessageBox.Show("Save clicked for the book '" + Title + "'.");
}
protected virtual bool CanSaveCommand()
{
return true;
}
}
That is a very basic example of what you would probably want to do. I wanted to keep it simple to not take away from the example, you will probably want to at least do some null checking.
With the above, you shouldn't have to change your UserControl any, I had to add the row and column definitions, but I ended up with the following:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">Title</Label>
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{Binding Title}" />
<Label Grid.Row="1" Grid.Column="0">Author</Label>
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{Binding Author}" />
<Label Grid.Row="2" Grid.Column="0">Description</Label>
<TextBlock Grid.Row="2"
Grid.Column="1"
Text="{Binding Description}" />
<Button Grid.Row="3"
Grid.Column="0"
Command="{Binding SaveCommand}"
Content="Save" />
</Grid>
Hopefully you noticed that our BookViewModel's constructor accepts a book, so that means that we need to change our ContainerViewModel to house the proper collection and create them correctly.
public class ContainerViewModel
{
private ObservableCollection<BookViewModel> books;
public ContainerViewModel()
{
Books.Add(
new BookViewModel(new BookModel
{
Title = "A Book 2",
Author = "Some Author",
Description = "Its a really good book!"
}));
Books.Add(
new BookViewModel(new BookModel
{
Title = "A Book 3",
Author = "Some Author",
Description = "Its a really good book!"
}));
}
public ObservableCollection<BookViewModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookViewModel>();
}
return books;
}
}
}
All that and your ItemsControl can simply be as follows:
<ItemsControl ItemsSource="{Binding Path=Books}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyBookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Related

How can I DataBind a textbox from the parent window with values from the child with MVVM?

I just took over a project from another programmer who is no longer here. It was created using the MVVM Pattern (using the MVVM Light toolkit). I am new to MVVM and have been trying to learn the basics fast. Currently I am having trouble getting a selected value from a Child Window back to the Parent Window.
From another post on SO I learned that I should use the same ViewModel for both the parent and the child so I think I have the basics right. However I have not been able to get the selected values back to the parent. Below is a sample set of code similar to the production code.
My ViewModel for both pages is here
public class MainViewModel : ViewModelBase
{
private Vendor selectedVendor = null;
List<Vendor> vendors;
public MainViewModel()
{
OpenVendorWindowCommand = new RelayCommand(VendorSelect);
VendorSelectedCommand = new RelayCommand(VendorSelected);
LoadVendors();
}
public ICommand OpenVendorWindowCommand { get; private set; }
public ICommand VendorSelectedCommand { get; private set; }
void VendorSelect()
{
Messenger.Default.Send(new NotificationMessage("SelectVendor"));
}
public Vendor SelectedVendor
{
get { return selectedVendor; }
set
{
if (selectedVendor != value)
{
selectedVendor = value;
RaisePropertyChanged();
}
}
}
void VendorSelected()
{
Console.WriteLine(SelectedVendor.VendorName);
}
public List<Vendor> Vendors
{
get
{
return vendors;
}
set
{
if (vendors != value)
{
vendors = value;
RaisePropertyChanged();
}
}
}
private void LoadVendors()
{
DataTable dt = new DataTable();
dt = Vendor.GetVendors();
Vendors = new List<Vendor>();
foreach (DataRow row in dt.Rows)
{
Vendors.Add(new Vendor()
{
VendorID = Convert.ToInt32(row["VendorID"]),
VendorCode = Convert.ToString(row["VendorCode"]),
VendorName = Convert.ToString(row["VendorName"])
});
}
}
}
I am at the point that the Child Window opens and I am able to select a vendor from a ListBox. After the selection I press a button (VendorSelectedCommand) and it is at that point I want the textbox on the Parent Window to be filled with the SelectedVendor.VendorName value.
This is the XAML from my Child Window
<StackPanel VerticalAlignment="Center">
<ListBox
Height="200"
Margin="5"
HorizontalAlignment="Stretch"
Background="GhostWhite"
ItemsSource="{Binding Vendors}"
SelectedItem="{Binding Path=SelectedVendor, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="3">
<StackPanel Margin="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="175" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
FontWeight="SemiBold"
Foreground="Black"
Text="{Binding VendorName}" />
<TextBlock
Grid.Column="1"
FontWeight="SemiBold"
Foreground="Black">
<Run Text=" (" />
<Run Text="{Binding VendorCode}" />
<Run Text=") " />
</TextBlock>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding VendorSelectedCommand}" Content="Send Vendor Back" />
</StackPanel>
And lastly this is the XAML for the Parent Window with what I think is the correct binding
<StackPanel VerticalAlignment="Center">
<TextBox Margin="10" Text="{Binding SelectedVendor.VendorName}" />
<Button
Margin="10"
Command="{Binding OpenVendorWindowCommand}"
Content="Select Vendor" />
</StackPanel>
I have tried every possible combination of Binding Syntax that I can think of and have tried multiple different ways in the code behind to catch and bind it but have not been able to get it right. What is missing from my ViewModel to make this work?
Edit For clarity (and in response to a comment) I am adding the DataContext, which I had in the Constructor of the Views.
public partial class VendorView : Window
{
private MainViewModel _vm = null;
public VendorView()
{
InitializeComponent();
_vm = new MainViewModel();
DataContext = _vm;
}
}
Edit #2 I am opening the second page with this. This is very simple sample app with only two pages so I didn't want to get bogged down with navigation until I have a better handle on Binding.
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "SelectVendor")
{
var vendorView = new VendorView();
vendorView.ShowDialog();
}
}

Raise usercontrol commands from a parent window using mvvm pattern

My WCF application supports downloading books from a remote service. The user sends a request to the service to download a book, the service gets the request and downloads the book. in response, it sends the download progress for the requested book.
Each element in the BookContainer is a userControl of Book.xaml and its logic represented by bookviewmodel.cs class.
When the user clicks on each book element in the BookContainer.xaml window, the click command inside the bookviewmodel is raised as expected.
I need the help to implement "DownlaodAllCommand" inside containerviewmodel to raise each DownloadCommand of each book.
How should it be implemented using the mvvm pattern.
BookContainer view :
https://onedrive.live.com/redir?resid=3A8F69A0FB413FA4!124&authkey=!ANdfYAk6f0Vf-8s&v=3&ithint=photo%2cpng
code behind:
BookContainer.xaml:
<Window x:Name="BookContainer"
DataContext="{Binding Source={StaticResource Locator}, Path=ContainerViewModel}">
<DockPanel>
<ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding Books}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<Controls:BookControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button DockPanel.Dock="Bottom" Command="{Binding DownloadAllCommand}" Content="Download All" > </Button>
</DockPanel>
</Window>
ContainerViewModel
public class ContainerViewModel : ViewModelBase
{
private ObservableCollection<BookViewModel> books;
public ObservableCollection<BookViewModel> Books
{
get
{
if (books == null)
{
// Not yet created.
// Create it.
books = new ObservableCollection<BookViewModel>();
}
return books;
}
}
private ActionCommand _DownloadAllCommand;
public ICommand DownloadAllCommand
{
get
{
if (_DownloadAllCommand == null)
{
_DownloadAllCommand = new ActionCommand(OnDownloadAllCommand, CanDownloadAllCommand);
}
return _DownloadAllCommand;
}
}
private void OnDownloadAllCommand()
{
// help !
}
private bool CanDownloadAllCommand()
{
return true;
}
}
Book.xaml
<UserControl
<Label Grid.Row="0">Title</Label>
<Label Grid.Row="1">Author</Label>
<Label Grid.Row="2">Description</Label>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Title}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Author}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Description}"/>
<Button Grid.Column="2" Grid.RowSpan="3" Command="{Binding DownloadCommand}" Content="Download" />
<Ellipse Grid.Column="3"
Height="20" Width="20"
Stroke="Black"
StrokeThickness="0.5"
HorizontalAlignment="Center"
Grid.Row="1"
/>
<Controls:PieSlice Grid.Column="3" Grid.Row="1" Stroke="Black" Fill="Black"
Height="20" Width="20"
StartAngle="0" EndAngle="{Binding Percent}"
HorizontalAlignment="Center" />
</UserControl>
BookViewModel
public class BookViewModel : ViewModelBase
{
public delegate void DownloadRequest(string name);
public event DownloadRequest OnDownalodRequest;
public BookViewModel(BookModel model)
{
this.Book = model;
}
private ActionCommand _DownloadCommand;
public ICommand DownloadCommand
{
get
{
if (_DownloadCommand == null)
{
_DownloadCommand = new ActionCommand(OnDownloadCommand, CanDownloadCommand);
}
return _DownloadCommand;
}
}
protected virtual void OnDownloadCommand()
{
if (OnDownalodRequest != null)
{
OnDownalodRequest.Invoke(this.Author);
}
}
}
BookModel
public class BookModel
{
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public Status Status { get; set; }
}
Insid the ContainerViewModel, method OnSaveAllCommand():
Books.ToList().Foreach(book =>
{
if(book.SaveCommand.CanExecute() == false) return;
book.SaveCommand.Execute()
}
);
but take in account that this is a bad practice to use a viewmodel from another viewmodel, try to perform viewmodels communnication on a model level.

Wpf adding stack panels from view

I'm not sure my title is clear (poor wpf skills).
What i'm trying to do is to create a smart data entry form. My goal is to have a hard coded data that the user should enter, and on demand (a plus button) he can enter another set of data, every time the user will click the plus button another set will appear in the window (endless)
Edit:
For more details, for a very simple example of what i'm trying to achieve, lets say that this is the window:
And after the user will click the plus button the window will look like this:
And the plus button will always let the user adding more peoples.
Seems like all you need is a List and a ItemControl:
Your Model:
public class User
{
public String Name { get; set; }
public int Age { get; set; }
}
In your ViewModel:
public List<User> Users { get; set; }
//In your constructor
Users = new List<User>();
In your View:
<ItemsControl ItemsSource={Binding Users}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And then below this wire up your add button to a command to point to a method that would do someething like:
private void AddUser()
{
Users.Add(new User());
NotifyPropertyChange("Users");
}
Use an ItemsControl with its ItemsSource property bound to a ReadOnlyObservableCollection<Person>, where Person is a class holding the name and age as strings.
(1) Create Person
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
(2) Create PeopleViewModel, holding your collection.
public class PeopleViewModel
{
private ObservableCollection<Person> _people;
public ReadOnlyObservableCollection<Person> People { get; private set; }
public PeopleViewModel()
{
_people = new ObservableCollection<Person>();
People = new ReadOnlyObservableCollection<Person>(_people);
addPerson(); // adding the 1st person
}
// You also need to hook this up to the button press somehow
private void addPerson()
{
_people.Add(new Person());
}
}
(3) Set the DataContext of your window to be a PersonViewModel in the code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new PeopleViewModel();
}
}
(4) Create an ItemsControl along with a DataTemplate for Person
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Don't forget to hook up your button either through a Command or through the Button.Click event.

WPF Control losing focus when clicking on a tab

On a tabcontrol I have several tabpages, on one of the tabs there is a textbox in the content.
This textbox is content bound with a simple Path=PropertyName and UpdateSourceTrigger=LostFocus. The reason I am using LostFocus is I trap the Lost focus event of the Textbox and possibly reformat the text. This is a "time" textbox and if they enter "0900", I want to reformat to "09:00". This part works great when I press the tab key to move to the next control, but if I type "0900" then press one of the other tabs, I hit the lost focus and re-format the value in the textbox, BUT the bind never gets called to update my object. When I come back to the tab, the value is blanked out (or reset to the original value on the object)
Any ideas why textbox does not trigger the Binding update when changing tab page?
Note: this also happens with a regular textbox that does wire to the lost focus event. It seems to have something to do with click on the tab.
[[Added Code ]]
More notes:
1. I am dynamically creating the tabs and controls on the tab (not sure if that has something to do with it or not)
2. I am using the Prism libraries
MainWindow Xaml
<Window.Resources>
<DataTemplate DataType="{x:Type ctrls:myTextBoxDef}">
<Grid Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding LabelText}" />
<TextBox Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding DocValue,
Mode=TwoWay,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=LostFocus}"
/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Tabs, Mode=OneWay}"
SelectedItem="{Binding SelectedTab,
Mode=TwoWay,
NotifyOnSourceUpdated=True}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="18,14,22,0"
Text="{Binding HeaderText}" />
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<AdornerDecorator Grid.Column="0">
<ItemsControl Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsTabStop="False"
ItemsSource="{Binding Controls,
Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Grid.Column="0"
Margin="10,5,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</AdornerDecorator>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Main Window Code Behind
public partial class MainWindow : Window
{
private DataContextObject obj = new DataContextObject();
public MainWindow()
{
InitializeComponent();
myTextBoxDef txt1 = new myTextBoxDef(obj, "Textbox 1", "TAB1TextBox1");
myTextBoxDef txt1b = new myTextBoxDef(obj, "Textbox 1 value", "TAB1TextBox1");
myTextBoxDef txt2 = new myTextBoxDef(obj, "Textbox 2", "TAB1TextBox2");
myTextBoxDef txt2b = new myTextBoxDef(obj, "Textbox 2 value", "TAB1TextBox2");
obj.Tabs.Add(new myTabDef("Tab 1", new ObservableCollection<myTextBoxDef>() { txt1, txt2 }));
obj.Tabs.Add(new myTabDef("Tab 2", new ObservableCollection<myTextBoxDef>() { txt1b, txt2b }));
obj.SelectedTab = obj.Tabs[0];
this.DataContext = obj;
}
}
Supporting objects
public class DataContextObject : NotificationObject
{
List<myTabDef> _tabs = new List<myTabDef>();
public List<myTabDef> Tabs
{
get
{
return _tabs;
}
}
private myTabDef _item;
public myTabDef SelectedTab
{
get
{ return _item; }
set
{
_item = value;
this.RaisePropertyChanged("SelectedItem");
}
}
private string _txt1 = "";
public string TAB1TextBox1
{
get { return _txt1; }
set
{
_txt1 = value;
this.RaisePropertyChanged("TAB1TextBox1");
}
}
private string _txt2 = "";
public string TAB1TextBox2
{
get { return _txt2; }
set
{
_txt2 = value;
this.RaisePropertyChanged("TAB1TextBox2");
}
}
private string _txt3 = "";
public string TAB2TextBox1
{
get { return _txt3; }
set
{
_txt3 = value;
this.RaisePropertyChanged("TAB2TextBox1");
}
}
}
public class myTabDef
{
public myTabDef(string tabText, ObservableCollection<myTextBoxDef> controls)
{
HeaderText = tabText;
_left = controls;
}
public string HeaderText { get; set; }
private ObservableCollection<myTextBoxDef> _left = new ObservableCollection<myTextBoxDef>();
public ObservableCollection<myTextBoxDef> Controls
{
get
{
return _left;
}
}
}
public class myTextBoxDef : NotificationObject
{
public myTextBoxDef(NotificationObject bound, string label, string bindingPath)
{
LabelText = label;
Path = bindingPath;
BoundObject = bound;
BoundObject.PropertyChanged += BoundObject_PropertyChanged;
}
public string LabelText
{
get;
set;
}
public NotificationObject BoundObject
{
get;
set;
}
public string DocValue
{
get
{
return PropInfo.GetValue(BoundObject, null) as string;
}
set
{
PropInfo.SetValue(BoundObject, value, null);
}
}
protected virtual void BoundObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(Path))
{
this.RaisePropertyChanged("DocValue");
}
}
public string Path
{
get;
set;
}
private PropertyInfo pi = null;
protected PropertyInfo PropInfo
{
get
{
if (pi == null && BoundObject != null && !string.IsNullOrEmpty(Path))
{
PropertyInfo[] properties = BoundObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
pi = properties.Where((prop) => string.Compare(prop.Name, Path, true) == 0).FirstOrDefault();
}
return pi;
}
}
}
We have found a solution. I came cross this set of postings
https://groups.google.com/forum/#!topic/wpf-disciples/HKUU61A5l74
They talk about a control called TabControlEx. Towards the bottom (5th from the bottom) you will see a posting by Sacha Barber that has a zip file with an example.
It solved all our problems we were having.
here is also another link where the code for the Class is posted
http://updatecontrols.codeplex.com/discussions/214434

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