WPF binding user-control in DataTemplate - c#

I am struggling to bind my user-defined tooltip to the items of a TreeView.
This is the XAML inside the TreeView: as you can see I bind my HierarchicalDataTemplate to the GroupWrapper (simple Class that exposes the properties Name and Children) and the DataTemplate to the DocWrapper (again, simple Class that has properties such as Name, Icon, BsonContent).
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type tmistruct:GroupWrapper}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type tmistruct:DocWrapper}" >
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}"/>
<TextBlock Text="{Binding Name}" Tag="{Binding BsonContent}" VerticalAlignment="Center" Style="{StaticResource TreeViewTextboxItemStyle}"/>
<StackPanel.ToolTip>
<local:MyDocTooltip
NameField="{Binding Name}"/>
</StackPanel.ToolTip>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
It all works great! The Tree is automatically populated with the correct text and icons for all nodes BUT... my user-defined tooltip doesn't get the memo. The tooltip shows, but the name field is not populated. Note that if I replace
<local:MyDocTooltip
NameField="{Binding Name}"/>
with:
<local:MyDocTooltip
NameField="TESTSTRING"/>
The test string is correctly displayed in the Tooltip.
This is what my user-defined tooltip looks like:
namespace MyControls
{
/// <summary>
/// Interaction logic for MyDocTooltip.xaml
/// </summary>
public partial class MyDocTooltip : UserControl
{
public MyDocTooltip()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty NameFieldProperty = DependencyProperty.Register("NameField", typeof(string), typeof(MyDocTooltip));
public string NameField
{
get { return (string)GetValue(NameFieldProperty); }
set { SetValue(NameFieldProperty, value); }
}
}
}
I guess something to do with my tooltip's data context being set to its own ViewModel? I've been trying with relative references but with no luck. Any help appreciated! Cheers.

OK my whole setup was wrong. (Thanks #ASh)
For those out there who are struggling with similar issues:
Each user control should not set its DataContext property!
This should be inherited.
What you do inside each user control, is to bind each element to its UserControl ancestor.
So, inside MyDocTooltip.xaml each element will be defined like this (bound to NameField property):
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=NameField}"/>
Then I do the same in the TreeView (which is also inside a user control):
<TreeView x:Name="treeViewCollection" Grid.Row="2" Grid.ColumnSpan="2" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}},Path=FilteredGroups, UpdateSourceTrigger=PropertyChanged}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type tmistruct:GroupWrapper}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate x:Name="TreeViewDataTemplate" DataType="{x:Type tmistruct:DocWrapper}" >
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}"/>
<TextBlock Text="{Binding Name}" Tag="{Binding BsonContent}" VerticalAlignment="Center"/>
<StackPanel.ToolTip>
<local:MyDocTooltip
NameField="{Binding Name}"
/>
</StackPanel.ToolTip>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And it all seems to work fine. Please do let me know if I'm still doing it wrong. Thanks for the help.

Related

How to make same color for treeview item and parent?

I am creating Treeview in wpf.
Each parent item set random foreground color using converter.
I want all its children to set the same color. using only xaml.
Here is part of my code:
<HierarchicalDataTemplate DataType="{x:Type sotc:Category}"
ItemsSource="{Binding Path=NoteList}">
<TextBlock Text="{Binding Path=Name}" Forground="{Binding Path=Name, Convert={StaticResource Converter1}}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type sotc:Note}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
What I wish to do is to set the second treeview item (in the xaml code), Children in HierarchicalData the same forgound color as its parent.
Is there a way to do that?
Thank Ahead,
You can try by using the FindAncestor RelativeSource mode.
Just replace your Note template with this one:
<DataTemplate DataType="{x:Type sotc:Note}">
<TextBlock Text="{Binding Path=Name}"
Foreground="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=2, AncestorType=TreeViewItem}, Path=Header.Name, Converter={StaticResource Converter1}}"/>
</DataTemplate>
I used a DataTemplate, but you can keep using a HierarchicalDataTemplate if you prefer.

WPF - I'm stumped on the TreeView control

Pun intended.
I want to create a simple TreeView using the HierarchicalDataTemplate class.
Here's my problem XAML:
<Window.Resources>
<ObjectDataProvider
x:Key="myDataProvider"
ObjectType="vm:ContractViewModel" />
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource myDataProvider}" Path="Contract" />
</Window.DataContext>
<StackPanel
Orientation="Vertical"
VerticalAlignment="Top">
<ListBox MinWidth="400" Margin="10"
ItemsSource="{Binding Commissions}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Id}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TreeView>
<HierarchicalDataTemplate DataType="{x:Type m:Contract}"
ItemsSource="{Binding Commissions}">
<TextBlock Text="{Binding Id}" />
</HierarchicalDataTemplate>
</TreeView>
</StackPanel>
I'm using the MVVM pattern. The StaticResource "myDataProvider" returns an instance of a Contract (custom) class. Here's my model:
internal class Contract
{
public string Name { get; set; }
public ObservableCollection<Commission> Commissions { get; set; }
}
internal class Commission
{
public string Id { get; set; }
}
FYI - my model is actually more complex; my classes contain more members than shown, they have constructors, and they implement INotifyPropertyChanged.
In my test, I load two Commission objects into a Contract object. The listbox works as expected: I can see the Id of each Commission object w/in Contract. The TreeView doesn't work: it returns a "System.Windows.HierarchicalDataTemplate" string in the TreeView control where I'd expect each Commission Id to be listed.
I've referred to other posts and MSDN to no avail. I'd be appreciative of your help!
From what I can tell is that you're not using the TreeView correctly in XAML. You need to put your HierarchicalDataTemplate at the TreeView.Resources level and assign a ItemsSource
As shown here you want to set the Template of the item.
Try to do something like this instead:
<TreeView ItemsSource="{Binding Contracts}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:Contract}"
ItemsSource="{Binding Commissions}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type m:Commission}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Id}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I personally do it like this--using RadTreeView:
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:BaseType}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>

Multiple level bindings with DataTemplate based on type

I'm trying to make a complicated template for ListBox item, but some bindings not working or "Two-way binding requires Path or XPath." exception occurred.
I have a ListBox binding on ObservableCollection of CustomerViewModel. Each item in ListBox must display two properties of CustomerViewModel object. The first one is "Name" (type - string), and second one is "Value" (type - object). Real type of "Value" can be: bool, int, string, datetime, so the control template for displaying must be select-able.
Code of "Value" property:
public object Value
{
get
{
switch (this.info.InputType)
{
case Enums.InputType.DateTime:
return Convert.ToDateTime(this.info.Value);
case Enums.InputType.Logical:
return Convert.ToBoolean(this.info.Value);
case Enums.InputType.Numeric:
return Convert.ToInt32(this.info.Value);
case Enums.InputType.Text:
return this.info.Value;
}
throw new ArgumentOutOfRangeException();
}
set
{
this.info.Value = value.ToString();
this.RaisePropertyChanged("Value");
}
}
XAML:
<ListBox ItemsSource="{Binding Customers}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}"/>
<ContentControl Content="{Binding Value}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type system:String}">
<TextBox Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type system:Boolean}">
<CheckBox IsChecked="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type system:Int32}">
<TextBox Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type system:DateTime}">
<DatePickerTextBox Text="{Binding}"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
XAML below throws "Two-way binding requires Path or XPath" exception, because of
<ContentControl Content="{Binding Value}">
and
<TextBox Text="{Binding}"/>
If I change last line to:
<TextBox Text="{Binding Path=.}"/>
..there are no exceptions, but binding works only "OneWay".
I think, I need, somehow, to bind TextBox to same property - "Value" as ContentControl, but I can't perform TwoWay binding in this case.
Could it be done like this without writing of ItemTemplateSelector?
Have you tried your last line with setting the mode?
<TextBox Text="{Binding Path=., Mode=TwoWay}"/>
EDIT:
I noticed you are using this.fieldInfo.Value in the get and this.info.Value in the set, is this just a typo?
Are you sure you are not able to hit a break point placed in your set?
EDIT:
Hmm, I'm running out of ideas, maybe you could try setting the update source trigger?:
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

Nested databinding with WPF and C#

I'm trying to make a budget program. Where I need to have groupboxes with a list of textblocks inside.
<ItemsControl DataContext="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding}">
<ItemsControl DataContext="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I need somehow to databind a list (perhaps?) with groupboxes so I'd create a list of group boxes, with some lines inside that would be a text with a currency value. So that I could create a group called "Apartment", with two lines "Rent $3000" and "Maintenance $150". Then I could have a second group called "Car" with lines "Insurance", "Loan" and "Maintenance" for instance.
But how would I databind this? And how would I need in C# to perform this. I'm at a loss.
Building off of Jay's comment, you would want to create a Hierarchical data model. Note I have left implementing INotifyPropertyChanged on the properties to you
public class BudgetLineItem : INotifyPropertyChanged
{
public string Name { get; set; }
public decimal Cost { get; set; }
}
public class BudgetGroup : INotifyPropertyChanged
{
public string GroupName { get; set; }
public ObservableCollection<BudgetLineItem> LineItems { get; set; }
}
public class BudgetViewModel : INotifyPropertyChanged
{
public ObservableCollection<BudgetGroup> BudgetGroups { get; set; }
}
Then your data-template would look like this:
<ItemsControl DataContext="{Binding ViewModel}"
ItemsSource="{Binding BudgetGroups}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding GroupName}">
<ItemsControl ItemsSource="{Binding LineItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Cost}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I could be off base here, but it sounds like you want to change the DataTemplate based on the type of object that is being bound from a list of heterogeneous objects.
If that's the case, you want to look into DataTemplateSelectors or create DataTemplates for each of the types you want to support in the list.
For example, for an Apartment you might have:
<DataTemplate DataType="local:ApartmentBudget">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
a Car may look like:
<DataTemplate DataType="local:CarBudget">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Insurance}" />
<TextBlock Text="{Binding Loan}" />
<TextBlock Text="{Binding Maintenance}" />
</StackPanel>
</DataTemplate>
Then your ItemsControl can be set like:
<ItemsControl ItemSource="{Binding BudgetItems}">
The correct DataTemplate will be picked based on the data type. You can have even more control by creating a custom DataTemplateSelector.
See https://msdn.microsoft.com/en-us/library/ms742521(v=vs.100).aspx for more information.

Find TreeViewItem to drop data

I want to drag data from a ListView and drop it in a TreeView(the draging works fine). I use DataBinding and ItemTemplate to fill the TreeView.
<TreeView ItemsSource="{Binding Groups}" Name="tvGroups" AllowDrop="True"
Drop="tvDrop" DragOver="tvDragOver">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Participants}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Button Tag="{Binding .}" Click="Button_Click_2">
<Image Source="Resources/cross.png" />
</Button>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Alias}" />
<Button Tag="{Binding .}" Name="btnDeleteParticipants" Click="btnParticipants_Click" >
<Image Source="Resources/cross.png" />
</Button>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
private void tvDrop(object sender, DragEventArgs e)
{
if (e.Effects == DragDropEffects.Copy &&
e.Data.GetDataPresent(typeof(Participant)))
{
Participant data = e.Data.GetData(typeof(Participant)) as Participant;
}
}
A Participant is dragged from the ListView to the TreeView. Now I need to find the Group. Any ideas where to get the right Group from the TreeView?
I would simply set the Drop="tvDrop" and DragOver="tvDragOver" on the StackPanel in the HierarchicalDataTemplate's ItemTemplate.
This way
1) You don't have any risk of getting an event when something is dropped out of a group
2) You can safely cast the Sender to a FrameworkElement and get the DataContext and cast it to your class.
You can also set a different handler on the treeview itself if you need to support dragging out of the groups.

Categories

Resources