WPF LayoutTransform: ToolTip size not modified - wpf

I'm using LayoutTransform in my windows' top level 'Grid' control, to do perform a ScaleTransform and implement a zoom factor on the UI (similar to how browsers do it).
Things work well, but somehow tooltips show up with the unadjusted size.
If there a way to for example enumerate all toolTips in a window and adjust their size from the .cs file? ...or any other way to deal with this?

The reason why the tooltips are unaffected is because tooltips are popups that are not part of the window's visual tree. Thus, any layout transformation performed on the componenets of the window will not carry over to the tooltips. If you have mostly generic tooltips (text basically), you can create a non-keyed style in your window resources and WPF will automatically apply that to all your tooltips:
<Window.Resources>
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolTip}">
<Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<Border.LayoutTransform>
<ScaleTransform ScaleX="{Binding ScaleFactor}"></ScaleTransform>
</Border.LayoutTransform>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel x:Name="MainPanel">
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="{Binding ScaleFactor}" ScaleY="{Binding ScaleFactor}"></ScaleTransform>
</StackPanel.LayoutTransform>
<TextBlock ToolTip="blah">haha!</TextBlock>
</StackPanel>
In the example I have ScaleX/Y for the tooltip bound to a ScaleFactor property on my window's View Model. You can keep it dynamic this way I believe.

Like what Rowbear has mentioned, Tooltips are popups (which are windows), so they have their own visual tree. But, as far as I know, the big problem is, these popups do not inherit DataContext from the spawning control, too.
<Window.Resources>
<Style TargetType="ToolTip">
<Setter Property="LayoutTransform" Value="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.LayoutTransform}" />
</Style>
</Window.Resources>

Related

Data binding in custom WPF controls

I'm completely stuck trying to bind an image to my custom WPF Expander.
I found an examle for creating expander template here: https://www.codeproject.com/Articles/248112/Templating-WPF-Expander-Control and tried to edit this to use an image instead of expander icon.
Here is my custom template for expander button (I added an image source here, so it works properly with straight resource path, not binding):
<ControlTemplate x:Key="SimpleExpanderButtonTemp"
TargetType="{x:Type ToggleButton}">
<Border x:Name="ExpanderButtonBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image
Height="35"
Width="35"
Source="{Binding Path = ImageSource,
RelativeSource={RelativeSource TemplatedParent}}">
</Image>
<ContentPresenter x:Name="HeaderContent"
Grid.Column="1"
Margin="4,0,0,0"
ContentSource="Content"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!-- MouseOver, Pressed behaviours-->
<Trigger Property="IsMouseOver"
Value="true">
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Afterwards, I add template to expander itself:
<ControlTemplate x:Key="SidePanelExpander" TargetType="Expander">
<DockPanel>
<ToggleButton x:Name="ExpanderButton"
DockPanel.Dock="Top"
ImageSource="{Binding Path = ImageSource,
RelativeSource={RelativeSource TemplatedParent}}"
Template="{StaticResource SimpleExpanderButtonTemp}"
Content="{TemplateBinding Header}"
IsChecked="{Binding Path=IsExpanded,
RelativeSource={RelativeSource TemplatedParent}}"
OverridesDefaultStyle="True"
Padding="1.5,0">
</ToggleButton>
<ContentPresenter x:Name="ExpanderContent"
Visibility="Collapsed"
DockPanel.Dock="Bottom"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="ExpanderContent"
Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
And then I'm trying to use it this way:
<Expander ExpandDirection="Right"
Template="{StaticResource SidePanelExpander}"
ImageSource="../Res/Images/engine.png"
>
I guess there are some difficulties in data binding forwarding through templates, but have no idea on how to solve this.
As Clemens said in the comments, ToggleButton and Expander don't have a property called ImageSource, so this code was never going to work. The "correct" way to do this would be to create a custom control, but a quick fix (hack) would be to specify the image path using the Tag property:
In the "SimpleExpanderButtonTemp" template, change the Image element Source as follows:
<Image Height="35"
Width="35"
Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" />
Next, in the "SidePanelExpander" template ToggleButton, get rid of the "ImageSource=..." line and replace it with this:
Tag="{TemplateBinding Tag}"
Finally, in the control itself, specify your image:
<Expander Tag="../Res/Images/engine.png"
...
Also, in the first template, I've noticed you use TemplateBindings on certain properties of the Border control (Background, BorderBrush, BorderThickness). These won't work either. To fix this, copy those same lines to the ToggleButton control of the second template, i.e.
<ToggleButton x:Name="ExpanderButton"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
...
You can now specify (say) a background colour in the control itself:
<Expander Background="Blue"
...
The TemplateBindings on the ToggleButton act like stepping stones, allowing the property value to pass from the control itself to the ToggleButton, then on to the Border.

GridViewHeaderRowPresenter scrolls with custom TreeListView control

I have a custom TreeListView - based on the standard TreeView - with the following style:
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type: c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As I have no ScrollViewer, the content will not scroll so if the content requires more space than is available, it looks like this:
So I added a ScrollViewer so that the style now Looks like this:
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type: c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="False" Padding="{TemplateBinding Padding">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
With the content expanded as in the first Image, the Content is not able to scroll, but the column Header scrools as well, which is not wished for :D
Could anyone please help me so that only the content gets scrolled, and not the columns bound to the GridViewHeaderRowPresenter?
Thank you!
[EDIT]
Thanks to the related links to the right, I found the answer here.
Instead of having the DockPanel within the ScrollViewer I needed only to have the ItemsPresenter inside the ScrollViewer. So with the following Style it now works.
<Style TargetType="{x:Type c:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<GridViewHeaderRowPresenter DockPanel.Dock="Top" Columns="{Binding Path=Columns, RelativeSource={RelativeSource TemplatedParent}}" />
<ScrollViewer Focusable="False" Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Many thanks to Nikhil Agrawal for his code!
The solution given is incomplete and will only work if you have a fixed width control. By only wrapping the ItemsPresenter the header columns will not scroll horizontally if needed. This results in shifted value columns which then mismatch the headers.
I've tried applying SelectiveScrollingGrid.SelectiveScrollingOrientation="Horizontal" to GridViewHeaderRowPresenter but haven't been successfull with that approach.
Instead, you could add a scroll viewer to the GridViewHeaderRowPresenter and to the ItemsPresenter and then synchronize them. It's not the best solution, but at least it works.
<Style TargetType="{x:Type local:TreeListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TreeListView}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<ScrollViewer DockPanel.Dock="Top"
HorizontalScrollBarVisibility="Hidden"
Name="scrollViewerHeader"
VerticalScrollBarVisibility="Disabled">
<GridViewHeaderRowPresenter Columns="{StaticResource gvcc}"/>
</ScrollViewer>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
Name="scrollViewerBody"
VerticalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
And the code behind:
public class TreeListView : TreeView
{
private ScrollViewer _scrollViewerHeader;
private ScrollViewer _scrollViewerBody;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_scrollViewerHeader = (ScrollViewer)Template.FindName("scrollViewerHeader", this);
_scrollViewerBody = (ScrollViewer)Template.FindName("scrollViewerBody", this);
_scrollViewerBody.ScrollChanged += (sender, e) =>
{
_scrollViewerHeader.Width = e.ViewportWidth;
_scrollViewerHeader.ScrollToHorizontalOffset(e.HorizontalOffset);
};
}
}
The way DataGrid solves it is it customizes the Template of the ScrollViewer to place the GridViewHeaderRowPresenter outside the ScrollViewer ScrollContentPresenter:
https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L987-L993
This way when the scrollbars scroll, they don't impact the header row.
The ListView that uses GridView does a similar thing here:
https://github.com/dotnet/wpf/blob/059ba38771aef10d2172a3ca0e247dfbfa8cefde/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Aero2/Themes/Aero2.NormalColor.xaml#L2628
At the same time they wrap the header row in another ScrollViewer, and honestly I don't understand why.
Once I find out more I will expand on this answer. I think this approach if it works is cleaner than synchronizing scroll viewers.

WPF toolkit, shared tooltip between multiple lineseries

I'm working with multiple time-series chart based on WPF toolkit. For example time-series of temperature, dewpoint and pressure in one chart
I need to share tooltips to show at each datapoint a summary of the meteorlogical parameters at a certain date/time in a little tooltip frame.
If anyone know if it is possible and how to do that, it would be great.
Thanks,
PY
Look up Styles for the DataPoints of your Series i've got one example for BubbleDataSeries:
<Style x:Key="BubbleToolTipTemplate" TargetType="{x:Type c:BubbleDataPoint}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="c:BubbleDataPoint">
<Grid>
<Ellipse Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" />
<ContentPresenter Content="{TemplateBinding Size}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<ToolTipService.ToolTip>
<StackPanel>
<ContentControl Content ="{TemplateBinding DependentValue, Converter={StaticResource DoubleToStringConverter}}" />
<ContentControl Content ="{TemplateBinding IndependentValue}"/>
<ContentControl Content ="{TemplateBinding Size}" />
</StackPanel>
</ToolTipService.ToolTip>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Control margins of an automatically generated content presenter in Windows Phone Panorama Control

I am building an MVVM Panorama Windows Phone 7 application.
At some point of Panorama Item's layout I get a bottom margin of a panorama header box, that moves my content too far down. Is there a way I can set a bottom margin of a ContentPresenter, that is generated to hold the controls, defined in the Panorama.HeaderTemplate?
Here is my layout list in Silverlight Spy:
In case the screen shot is not readable, here is a large version:
http://bit.ly/rBvNp8
Something generates a 26 points bottom margin for a header box (probably the control's code, that handles a layout). How can I control this value? I need it to be set to 0.
In order to control a ContentPresenter's properties one needs to redefine the default template (within a style setter) for the PanoramaItem. In my particular case it is PanoramaItem's style.
<Style TargetType="controls:PanoramaItem">
<Setter Property="CacheMode" Value="BitmapCache"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:PanoramaItem">
<Grid Background="{TemplateBinding Background}" Margin="12,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ContentControl x:Name="header" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" FontSize="{StaticResource PhoneFontSizeExtraExtraLarge}" FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" Margin="10,-2,0,0">
<ContentControl.RenderTransform>
<TranslateTransform x:Name="headerTransform"/>
</ContentControl.RenderTransform>
</ContentControl>
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Grid.Row="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Setting Margin="10,-2,0,0" does the trick.

WPF Expander Button Styled so it is inside Expander Header

I am using the Expander control and have styled the header as shown in the picture below:
http://www.hughgrice.com/Expander.jpg
The problem I have is that I want the expander button to be contained within the header so that the line for the end of the header template aligns with the Expander content i.e. I ultimatly want to end up with something similar to the image below:
http://www.hughgrice.com/Expander.gif
Thanks in advance.
I see that you want to actually move the expander button into your HeaderTemplate, not just restyle it. This is easily done with FindAncestor:
First add a ToggleButton and bind its IsChecked property using FindAncestor, along these lines:
<DataTemplate x:Key="MyHeaderTemplate">
<Border ...>
<DockPanel>
<!-- Expander button -->
<ToggleButton
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor,Header,1}}"
Content=... />
<!-- Other content here -->
...
</DockPanel>
</Border>
</DataTemplate>
This adds an expand button inside the header template but does not hide the original button provided by the Expander. To do this I recommend you replace the Expander's ControlTemplate.
Here is a complete copy of Expander's ControlTemplate with the ToggleButton replaced with a simple ContentPresenter:
<ControlTemplate x:Key="ExpanderWithoutButton" TargetType="{x:Type Expander}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="3"
SnapsToDevicePixels="true">
<DockPanel>
<ContentPresenter
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
DockPanel.Dock="Top"
Margin="1"
Focusable="false" />
<ContentPresenter
x:Name="ExpandSite"
Visibility="Collapsed"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
Focusable="false" />
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility" Value="Visible" TargetName="ExpandSite"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
It might be used as follows:
<Expander Template="{StaticResource ExpanderWithoutButton}">
<Expander.HeaderTemplate>
<DataTemplate ...>
<Border ...>
<DockPanel>
<ToggleButton ...
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor,Header,1}}" />
... other header template content here ...
A simpler alternative would be to just set a negative margin in yourHeaderTemplate to cover the expander button. Instead of the ControlTemplate shown above, your DataTemplat would just contain something like this:
<DataTemplate ...>
<Border Margin="-20 0 0 0" ... />
Adjust the negative margin to get the look you want. This solution is simpler but inferior in that if you switch to a different system theme the required margin may change and your expander may no longer look good.
You will need to edit the Expander's Template, not the HeaderTemplate. The HeaderTemplate doesn't contain the expand button, just the content inside of it.
The default control template looks something like this:
<ControlTemplate TargetType="{x:Type Expander}">
<Border>
<DockPanel>
<ToggleButton x:Name="HeaderSite"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Content="{TemplateBinding Header}"
DockPanel.Dock="Top"
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter x:Name="ExpandSite" />
</DockPanel>
</Border>
</ControlTemplate>
I took out most of the attributes but left in the important stuff. Basically, you will want to add your customizations around the ToggleButton. That is what contains the expand button and the header content.
If you have Expression Blend, it makes this process much easier because you can simply edit a copy of the original template. Visual Studio doesn't really have this ability yet.

Resources