Silverlight RelativeSource of TemplatedParent Binding within a DataTemplate, Is it possible? - wpf

I'm trying to make a bar graph usercontrol. I'm creating each bar using a DataTemplate.
The problem is in order to compute the height of each bar, I first need to know the height of its container (the TemplatedParent). Unfortunately what I have:
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height, Converter={StaticResource HeightConverter}, Mode=OneWay}"
does not work. Each time a value of NaN is returned to my Converter. Does RelativeSource={RelativeSource TemplatedParent} not work in this context? What else can I do to allow my DataTemplate to "talk" to the element it is being applied to?
Incase it helps here is the barebones DataTemplate:
<DataTemplate x:Key="BarGraphTemplate">
<Grid Width="30">
<Rectangle HorizontalAlignment="Center" Stroke="Black" Width="20" Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height, Converter={StaticResource HeightConverter}, Mode=OneWay}" VerticalAlignment="Bottom" />
</Grid>
</DataTemplate>

To answer your question RelativeSource only works in a ControlTemplate it doesn't work in a DataTemplate.
Is there a reason why the Silverlight Toolkit Chart controls don't work for you in creating a bar graph (or a Column Chart as the Tookit refers to vertical set of bars).

Have you tried the ActualHeight property? It should return you a value. RelativeSource with the TemplatedParent mode will work in a data template, but it will return the content presenter of the templated control/item, not the control/item itself (which it does when used in a control template). To experiment, put a button in the data template, and assign that binding expression (without the path) to its Tag property. Handle its Click event, and put a breakpoint in the event handler. Now when you run the project and click on the button, the breakpoint will be hit in your code, and you can see the object that it is binding to from the Tag property of the button (which you can see from the sender parameter). Hope this helps...

Related

How do I properly set this button's binding to the parent ListView ItemsControl?

I have a custom button set up inside a ListView ItemTemplate. The Listview's ItemSource is bound to a collection of items, pretty standard. I have a few labels in the listview as well, and everything works fine except the button.
Binding the button to one of the properties won't work at all using {Binding buttonName} but it will sort of work if I use {Binding Items/buttonName, ElementName=listView} - the only problem is, when I do it this way, every single button in that listView will have the exact same buttonName.
Now the issue stems from my custom button's DataContext being set to Self; unfortunately, it has to be set to Self because the custom style I'm using needs this. If I try to change the button to a UserControl instead (with the button as a child, and the DataContext set on that), then I can't use the Command property of the button for some reason.
Here's a simplified version of my ListView making use of the custom button:
<ListView x:Name="listView" ItemsSource="{Binding MyPeopleData}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Label Content="{Binding PersonName}"/>
<ct:RevealButton Content="{Binding Items/recommendation, ElementName=listView}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As I said above, this will make every item in the listview use the same recommendation property rather than using it's own one.
If I try to use
<ct:RevealButton Content="{Binding recommendation}"/>
It just won't work, which makes sense given the DataContext of the custom button, which is below:
<Button x:Class="RevealButton" Width="{Binding Width}" Height="{Binding Height}" Background="{Binding ButtonBackground}" DataContext="{Binding RelativeSource={RelativeSource Self}}" Style="{DynamicResource ButtonRevealStyleC}" mc:Ignorable="d">
<Button.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{TemplateBinding Content}" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
So this ended up being an XY Problem. Because the modified style I was using had a poorly bound property, it was failing when the parent control didn't have a DataContext set to self.
This is what it was:
<SolidColorBrush Opacity="0.8" Color="{Binding ButtonBackground.Color}" />
And ButtonBackground was a dependency property exposed by my custom Button, so binding the style in this way meant it only worked if the Button's context was itself. Changing it to this:
<SolidColorBrush Opacity="0.8" Color="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ButtonBackground.Color}" />
Fixed the DataContext dependency, which in turn fixes the heirarchy of problems above.

Binding collection of objects on a tooltip

Can someone tell me how to bind the tooltip of a StackPanel with its children?
Here's some code I used:
<StackPanel>
... (some UI like grid, textblock, border, ...)
<StackPanel.ToolTip>
<ToolTip Placement="RelativePoint" Padding="0" HasDropShadow="False">
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel, AncestorLevel=3}, Path=Children}"
Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=StackPanel, AncestorLevel=3}, Path=ActualWidth}"
Height="11">
</ItemsControl>
</ToolTip>
</StackPanel.ToolTip>
<StackPanel>
I first tried with VisualBrush that was binded on a ToolTip, but this shows only the non-hidden controls, so when a child was hidden (invisible for the eye, not for the PC) in the StackPanel, then that child was also invisible in the ToolTip.
Also want to say that the binding with ActualWidth works. Now i have a tooltip that has the right measures, but there is no content in it (it's just a filled white space rectangle).
Someone please help me?? :)
FYI, what i want is the same like you bind on the Content property of a Textblock with his tooltip. The only difference is that i want to bind on a collection of objects instead of a string value.
The problem is probably caused by the fact the ToolTip is not part of the StackPanel's visual tree.
Therefore the StackPanel is not an Ancestor of the ToolTip -> hence why the RelativeSource binding wont work.
In WPF you are supposed to use MVVM, because it will allow you to bind always to data, instead of other controls. You have to think about WPF controls as data visualizers, not as data containers.
So, if you are using MVVM, just bind the Tooltip ItemsControl to your (observable?)collection of items.

Using TemplateBinding inside DataTemplates

I'm creating a custom control, and I'm having problems binding UI elements inside a DataTemplate to the custom control's dependency properties.
There's a content control in my control that should change its content and content template according to a certain property, so I bound it this way -
<ContentControl Content="{TemplateBinding ControlMode}" ContentTemplateSelector="{StaticResource TemplateSelector}"/>
The Content Template selector is defined this way -
<ns:TemplateSelector x:Key="TemplateSelector">
<ns:TemplateSelector.Template1>
<DataTemplate>
<TreeView ItemsSource="{TemplateBinding TreeSource}"/>
</DataTemplate>
</ns:TemplateSelector.Template1>
<ns:TemplateSelector.Template2>
<DataTemplate>
<ListView ItemsSource="{TemplateBinding ListSource}"/>
</DataTemplate>
</ns:TemplateSelector.Template2>
</ns:TemplateSelector>
The problem is that the TreeView and the ListView can't be bound to their itemssource with TemplateBinding due to this error for example -
"Cannot find TreeSourceProperty on the type ContentPresenter"
I've been looking around for an answer and I found this answer that simple states that this is impossible.
How to use template binding inside data template in custom control (Silverlight)
So if this really is impossible, how else could I bind the elements inside my template to the DependencyProperties of the CustomControl?
Thanks!
In WPF you can use a binding with RelativeSource targeting the "templated" control.
e.g.
{Binding TreeSource,
RelativeSource={RelativeSource AncestorType=MyCustomControl}}
Edit: If you have a break in a tree you could possibly work around that by passing that control around, e.g.
<ControlThatOwnsPopup
Tag="{Binding RelativeSource={RelativeSource AncestorType=MyCustomControl}}">
<Popup>...
<TreeView ItemsSource="{Binding PlacementTarget.Tag.TreeSource,
RelativeSource={RelativeSource AncestorType=Popup}}"/>

Adjust the Visibility property of a View not working

I've a strange problem with binding a boolean property to a View's Visibility property.
I have a 'main' View that contains a bunch of other Views as well as various other UIElements including Buttons, TextBoxes, Grids, StackPanels and some telerik controls. Some of the controls have their visibility bound to boolean properties on my ViewModel, such that when the property is positive they are shown, and when negative they are collapsed.
<Border Visibility="{Binding IsSectionShown,
Converter={StaticResource BoolToVisibilityConverter}}" >
This is working perfectly for me. Obvious I have trigger the notification event in the IsSectionShown setter, and the control's Visibility is adjusted accordingly.
Now I have a View which needs to have its visibility adjusted. The obvious implementation is
<vw:ActivityView DataContext="{Binding Activity}"
Visibility="{Binding IsPositive,
Converter={StaticResource BoolToVisibilityConverter}}" />
Does not work! My work around is to wrap my view inside a StackPanel and adjust the visibility of the StackPanel - and this works fine:
<StackPanel Visibility="{Binding IsPositive,
Converter={StaticResource BoolToVisibilityConverter}}">
<vw:ActivityView DataContext="{Binding Activity}" />
</StackPanel>
Any ideas as to why this is happening? Workaround is fine but I would like to identity the gap in my understanding.
For both of these to work, the IsPositive property would have to exist both inside Activity and one level up in the data context that Activity comes from. But that's probably not what you intended. Instead, you can use something like this so that the visibility comes from a different data context than the one that applies to the view itself:
<vw:ActivityView DataContext="{Binding Activity}"
Visibility="{Binding IsPositive, ElementName=ParentElement,
Converter={StaticResource BoolToVisibilityConverter}}" />
where ParentElement is the parent element that contains vw:ActivityView.

Puzzle - Dynamically change data template control from another data template

I have a DataTemplate that contains an Expander with a border in the header. I want the header border to have round corners when collapsed and straight bottom corners when expanded. What would best practice be for achieving this (bonus points for code samples as I am new to XAML)?
This is the template that holds the expander:
<DataTemplate x:Key="A">
<StackPanel>
<Expander Name="ProjectExpander" Header="{Binding .}" HeaderTemplate="{StaticResource B}" >
<StackPanel>
<Border CornerRadius="0,0,2,2">
This is the expander datatemplate:
<DataTemplate x:Key="B">
<Border x:Name="ProjectExpanderHeader"
CornerRadius="{Binding local:ItemUserControl.ProjectHeaderBorderRadius, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}"
Background="{StaticResource ItemGradient}"
HorizontalAlignment="{Binding HorizontalAlignment,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}},
Mode=OneWayToSource}">
<local:ItemContentsUserControl Height="30"/>
</Border>
</DataTemplate>
Bind the CornerRadius property to the Expander.IsExpanded property and attach an IValueConverter that returns rounded corners when false and straight bottom corners when true. It's not the most elegant, but it will get the job done.
The other way to do this, if using MVVM, would be to expose a boolean property and bind it to the Expander.IsExpanded property. Then expose another property for the CornerRadius, which checks the boolean property and returns the appropriate values. This is definitely the "best practice" way to go about this.
Another way to do this is by editing the control template. The argument can be made that this is the best practice, though I'm not sure I'm ready to commit to that.
It's straightforward to do this if you have Expression Blend. An advantage of editing the control template is that it separates the behavior of the Expander from your data template, so that you can reuse it across different types of data. A disadvantage is that you end up embedding the properties of the header's Border in the control template, so you can't really change them for any individual instance of the control. (Other disadvantages: you have to have Expression Blend, and it produces a big bolus of XAML that you have to put in your resource dictionary.)
In Expression Blend, create an empty page and put an Expander on it. Right-click on the Expander and pick "Edit Template/Edit a Copy...". Give it a name like "ExpanderRoundedCorners".
This will add about 200 lines of XAML to Page.Resources, but most of this is used to create the graphics on the expand button. Switch to XAML view and search for the ToggleButton named "HeaderSite". This is the expand button. Note that its Content property is set to {TemplateBinding Header}. We'll want to fix that.
Delete the Content property, and add a child element to the ToggleButton like this:
<ToggleButton.Content>
<Border x:Name="HeaderBorder" BorderBrush="Red" BorderThickness="2">
<ContentPresenter Content="{TemplateBinding Header}"/>
</Border>
</ToggleButton.Content>
Now find the trigger that makes ExpandSite visible when the ToggleButton is pressed. Add this setter to it:
<Setter TargetName="HeaderBorder" Property="CornerRadius" Value="4"/>
That's it. Now every time you create an Expander with the ExpanderRoundedCorners style, the header content will be enclosed in a Border whose corners are rounded when the Expander is expanded.
You'll probably want to jigger around with this a little more when you've got it working. At the least, you'll want to remove the Border from the header template in your style, since it's now part of the control template.

Resources