How to make a custom tool tip template in WPF XAML - c#

I have a number of buttons on a form (devcomponents ButtonDropDown controls to be precise).
I want to show a tool tip for each that contains a header at the top, an image on the left and a description on the right.
The header needs to be tied to the ButtonDropDown.Header, the image to the ButtonDropDown.Image. I also then need to define the description somewhere.
I've only been using WPF for a few weeks so I've not managed to find any answers via searching, though I have studied a few.
Below is my attempt at creating a template that really doesn't work at all:
<Style TargetType="dcr:ButtonDropDown">
<Setter Property="OverridesDefaultStyle" Value="True"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="dcr:ButtonDropDown">
<ContentControl>
<ContentControl.ToolTip>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Content="{TemplateBinding Header}" FontWeight="Bold" />
<Viewbox Grid.Row="1" Grid.Column="0" Width="64" Height="32" Margin="3">
<ContentControl Content="{TemplateBinding Image}" />
</Viewbox>
<Label Grid.Row="1" Grid.Column="1"
Content="{TemplateBinding ToolTip.Content}" />
</Grid>
</ContentControl.ToolTip>
</ContentControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I then define a button as follows:
<dcr:ButtonDropDown Header="-X" Command="{Binding MoveCommand}" CommandParameter="xMinus"
ImagePosition="Top" IsEnabled="{Binding UserConfiguration.Move.Visible}"
ToolTip="move x axis down">
<dcr:ButtonDropDown.Image>
<Viewbox Width="32" Height="32" Margin="3">
<ContentControl Content="{StaticResource minusXImage}" />
</Viewbox>
</dcr:ButtonDropDown.Image>
</dcr:ButtonDropDown>
Please could someone give me an idea how to go about this?

I've gone some way to answering this question.
The following style is defined for ToolTip:
<Style TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border Background="GhostWhite" BorderBrush="Gainsboro" BorderThickness="1">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.Header)}"
FontWeight="Bold" />
<Viewbox Grid.Row="1" Grid.Column="0" Width="64" Height="32" Margin="3">
<ContentControl Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.Image)}" />
</Viewbox>
<Label Grid.Row="1" Grid.Column="1"
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.ToolTip)}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I then define a button as above and the ToolTip text and header appear in the ToolTipas required.
The key is the binding:
Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToolTip}},
Path=PlacementTarget.(dcr:ButtonDropDown.Header)}"
Which finds the Tooltip as an ancestor of Label and casts its PlacementTarget into a ButtonDropDown
What doesn't work is the image. This appears in the ToolTip but is removed from the button.
This will also break any other controls' tooltips if they are not ButtonDropDown controls.
I'm beginning to think I'll need to create some custom controls that contain the information I require for the ToolTip for each control.

Related

Extend LayoutAnchorable to have use its own style

I am using AvalonDock in my application to have a Visual Studio look and feel. I would like to add some buttons and other controls to the header of the LayoutAnchorableControl as well as add some menu items to the drop down. I only want this to affect certain controls and not the entire layout.
I've been able to add buttons and controls to specific LayoutAnchorableControls by adding methods to the LayoutAnchorable that check for certain criteria and adding the controls to the theme. However, I feel there is a more robust solution I have yet to stumble on.
If you are using MVVM approach, you don't need to add methods to LayoutAnchorableControls. You can get it simply editing AvalonDock style.
For example, you may edit the LayoutDocumentTabItem style like the following:
<Style TargetType="{x:Type avalonDockControls:LayoutDocumentTabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type avalonDockControls:LayoutDocumentTabItem}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Following border is required to catch mouse events -->
<Border Grid.ColumnSpan="5" Background="Transparent" />
<TextBlock Grid.Column="0" Visibility="{Binding LayoutItem.Model.IsStatusQuadStringVisible,
RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BoolToVisibilityConverter}}"
Text="{Binding LayoutItem.Model.StatusQuadString,
RelativeSource={RelativeSource TemplatedParent}}" FontSize="{StaticResource SrkMainTouchControlFontSize}" HorizontalAlignment="Left" VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
<ContentPresenter HorizontalAlignment="Center"
Grid.Column="2"
VerticalAlignment="Center"
Content="{Binding Model,
RelativeSource={RelativeSource TemplatedParent}}"
ContentTemplate="{Binding DocumentHeaderTemplate,
Mode=OneWay,
RelativeSource={RelativeSource AncestorType={x:Type avalonDock:DockingManager},
Mode=FindAncestor}}"
ContentTemplateSelector="{Binding DocumentHeaderTemplateSelector,
Mode=OneWay,
RelativeSource={RelativeSource AncestorType={x:Type avalonDock:DockingManager},
Mode=FindAncestor}}" />
<!-- region TabItem Buttons -->
<StackPanel Grid.Column="4" Orientation="Horizontal"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<Button Margin="2,0"
Command="{Binding LayoutItem.Model.MyCustomCommand, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ButtonAvalonDocumentStyleKey}">
<Button.ToolTip>
<PriorityBinding>
<Binding Path="LayoutItem.Model.MyCustomCommand" RelativeSource="{RelativeSource TemplatedParent}" IsAsync="True"/>
</PriorityBinding>
</Button.ToolTip>
<Path Data="{StaticResource SrkGeometryMyCustomCommand}" Stretch="Uniform" Margin="5,0" Style="{StaticResource PathAvalonDocumentStyleWithStrokeKey}" />
</Button>
</StackPanel>
<!-- endregion TabItem Buttons -->
</Grid>
</Border>
</Setter.Value>
</Setter>
</Style>
By doing this, you are adding a new custom Button to the tabbed Doc's title, binded to an ICommand named MyCustomCommand.
Furthermore, you may adding DataTrigger to the style binded to a Visibility property on the ViewModel that can handle button visibility, based on the business logic of your application.
I hope it helps.

Styled Expander Control Template and not able to view Expanded Content on Click

I am just styling the Expander Control in WPF. I have defined only the styles in Expander Control Template, but I am not able to view the content of expander when I click on it.
I guess I have to define the expander trigers also ? but I don't know which triger and how to define it.
Also why I have to define triggers when I am just styling the expander.
<Window x:Class="ExpanderControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="0" Name="contentRow"></RowDefinition>
</Grid.RowDefinitions>
<!--Expander Header-->
<Border Background="AliceBlue"
Grid.Row="0"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5"
></ContentPresenter>
<ToggleButton Grid.Column="1">
<TextBlock>x</TextBlock>
</ToggleButton>
</Grid>
</Border>
<!--Expander Content-->
<Border Background="Aqua" Grid.Row="1">
<ContentPresenter Grid.Row="1"></ContentPresenter>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Expander Header="Expander" HorizontalAlignment="Left" Margin="205,95,0,0" VerticalAlignment="Top" Width="200">
<Grid Background="#FFE5E5E5">
<Menu>
<MenuItem Header="hi"></MenuItem>
</Menu>
</Grid>
</Expander>
</Grid>
</Window>
At the moment there is no action linked to ToggleButton. You need to utilize Expander.IsExpanded property by binding it
To ToggleButton.IsChecked
To Border.Visibility via BooleanToVisibilityConverter (custom or built in)
Set content row height to Auto
This way changing ToggleButton.IsChecked will change Expander.IsExpanded which in turn will affect visibility of content Border. It will also work when you change IsExpanded property from outside.
This is working XAML
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<Style TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto" Name="contentRow"/>
</Grid.RowDefinitions>
<!--Expander Header-->
<Border Background="AliceBlue" Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header" RecognizesAccessKey="True" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5" />
<ToggleButton Grid.Column="1" Content="x" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded}"/>
</Grid>
</Border>
<!--Expander Content-->
<Border Background="Aqua" Grid.Row="1" Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded, Converter={StaticResource BooleanToVisibilityConverter}}">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
EDIT
If you want whole header to be able to expand/collapse your Expander you need to bring ContentPresenter for Header into Content of ToggleButton. In your case basically bring header Grid into ToggleButton.Content
<!--Expander Header-->
<ToggleButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded}" HorizontalContentAlignment="Stretch" Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header" RecognizesAccessKey="True" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="5" />
<TextBlock Grid.Column="1" Text="x" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ToggleButton>

Default behavior of a textbox

I've got a custom control that is a textbox and two buttons inside a frame.
I'd like the frame to display the typical textbox border-behaviour. Currently, it just has a black border. Is there a short way or will I need 200 lines of XAML?
(I've found some examples e.g. the ComboBoxStyles-Example from MS, but .. these do use 200 lines of XAML).
This is the style for the IntegerSpinControl:
<Style TargetType="{x:Type cc:IntegerSpinControl}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type cc:IntegerSpinControl}">
<Border BorderThickness="1" BorderBrush="Black">
<DockPanel LastChildFill="True" HorizontalAlignment="Stretch">
<Grid DockPanel.Dock="Right" x:Name="grid1"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<RepeatButton Grid.Row="0"
Grid.Column="1"
Width="22"
Height="10"
Padding="0"
BorderThickness="0"
Command="{x:Static cc:IntegerSpinControl.IncreaseCommand}">
<RepeatButton.Content>
<Rectangle Width="16"
Height="5"
Fill="{StaticResource brush.scroll.up}" />
</RepeatButton.Content>
</RepeatButton>
<RepeatButton Grid.Row="1"
Grid.Column="1"
Width="22"
Height="10"
Padding="0"
BorderThickness="0"
Command="{x:Static cc:IntegerSpinControl.DecreaseCommand}">
<RepeatButton.Content>
<Rectangle Width="16"
Height="5"
Fill="{StaticResource brush.scroll.down}" />
</RepeatButton.Content>
</RepeatButton>
</Grid>
<TextBox DockPanel.Dock="Left"
BorderThickness="0"
HorizontalAlignment="Stretch"
Grid.Row="0"
Grid.Column="0"
Width="Auto"
Margin="0,0,1,0"
VerticalAlignment="Center"
IsReadOnly="True"
Text="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=FormattedValue,
Mode=OneWay}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There is already a control in the Extended WPF Toolkit that might be what you are looking for:
IntegerUpDown Control
Maybe you can use that instead?
BTW: There are also a Decimal and a Double version in the toolkit.
That being said, if you have to use your own Custom Control you will have to create the Templates and Styles yourself, however you can probably copy heavily from the Windows defaults.

Remove margin and padding dynamically WPF

I am currently doing a page with a form in it in WPF. There are two textboxes and one listbox with a button to add some elements in the listbox. In one mode (edit mode), I want everything to be visible so that the user will be able to edit the textboxes and add elements in the listbox. In the other mode (view mode), I binded the Visibility property so that when in this mode, everything except the listbox appears (and the listbox takes the whole place) so that the user can just see what existing elements are in the listbox.
Now my problem is, in the edit mode I give a Margin="10" margin, but in view mode I would like the listbox to take the full page width/height (so I would like to remove that margin). How would I go to do this?
My XAML (some user controls are encapsulated in a framework that I am using, but it shouldn't affect my question):
<Grid x:Name="MainScope" pres:OneTheme.Theme="Content">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="53*" />
</Grid.RowDefinitions>
<pres:OneContentLayout Mode="List" Grid.Row="3" Margin="10" >
<pres:OneListBox x:Name="ListBox" BorderBrush="{DynamicResource Presentation_ControlBorderBrush}" BorderThickness="1"
ItemsSource="{Binding InterfaceSpecification.IOPoints}"
DeleteItemRequestCommand="{Binding DeleteIOPointCommand}" IsEdited="{Binding IsEditable}" >
<pres:OneListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<vw:IOPointDefinitionView Grid.Column="0" />
<vw:IOPointOtherConnectedElementView Grid.Column="1" Margin="20,0"/>
<pres:OneIcon Grid.Column="2" IconBrush="{DynamicResource Icon_DraggableHandleHorizontal}" Margin="8,0" HorizontalAlignment="Right" Visibility="{Binding ElementName=ListBox, Path=IsEdited, Converter={StaticResource OneBooleanToVisibilityConverter}}" />
</Grid>
</DataTemplate>
</pres:OneListBox.ItemTemplate>
</pres:OneListBox>
</pres:OneContentLayout>
<pres:OneTextBox Watermark="Name..." Text="{Binding InterfaceSpecification.Name}" Margin="85,12,0,0"
AcceptsReturn="False" MaxLines="1" Height="22" VerticalAlignment="Top"
HorizontalAlignment="Left" Width="300" Visibility="{Binding IsEditable, Converter={StaticResource OneBooleanToVisibilityConverter}}" />
<pres:OneTextBox Margin="85,3.999,10,3" Text="{Binding InterfaceSpecification.Description}"
Watermark="Description..." Height="66" Grid.Row="1" Visibility="{Binding IsEditable, Converter={StaticResource OneBooleanToVisibilityConverter}}"/>
<pres:OneToggleButton x:Name="Add_Button" Content="Add IO Point" HorizontalAlignment="Right"
VerticalAlignment="Bottom" Margin="0,0,10,0" Grid.Row="2" Width="115" Height="18"
IsChecked="{Binding ElementName=Add_Popover, Path=IsOpen}"
pres:OneTheme.PresentationMode="Inline" Visibility="{Binding IsEditable, Converter={StaticResource OneBooleanToVisibilityConverter}}" />
...
So you basically want pres:OneContentLayout to have a margin of 0 when IsEditable is false and 10 when it's true, right?
You can use a Trigger in a Style to make that work like this:
<pres:OneContentLayout.Style>
<Style TargetType="{x:Type pres:OneContentLayout}">
<!-- Default margin is 0 -->
<Setter Property="Margin" Value="0" />
<Style.Triggers>
<!-- When in edit-mode make margin 10 -->
<DataTrigger Binding="{Binding IsEditable}" Value="True">
<Setter Property="Margin" Value="10" />
</DataTrigger>
</Style.Triggers>
</Style>
</pres:OneContentLayout.Style>

Keep popup within window bounds

I'm trying to display a popup under a button.
This is what it looks like now :
But I want it to stay within the window borders, something like this (example in paint)
This is my top bar with the popup declared at the bottom:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="55" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid x:Name="AdminContainer" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Background="Transparent">
<Viewbox StretchDirection="DownOnly" Stretch="Uniform" Height="35" HorizontalAlignment="Left" Margin="10,0,0,0">
<ContentControl Content="{StaticResource LynxLogo}" Margin="0,5" />
</Viewbox>
</Border>
<ToggleButton x:Name="AdminOptionsToggleButton"
Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
IsChecked="{Binding mainViewModel.OptionsPopupOpen, Mode=TwoWay}"
Style="{StaticResource EmptyToggleButtonStyle}"
Cursor="Hand">
<Grid x:Name="AdminPanel" HorizontalAlignment="Right" Height="45"
VerticalAlignment="Stretch" Margin="0,0,2,0" >
<Grid.Style>
<Style>
<Setter Property="Grid.Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ToggleButton}, Path=IsMouseOver}" Value="True">
<Setter Property="Grid.Background" Value="#FF404040"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=ToggleButton}, Path=IsChecked}" Value="True">
<Setter Property="Grid.Background" Value="#FF353535"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="45" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Padding="20,0,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--Name-->
<TextBlock Grid.Row="0" Text="{Binding adminViewModel.Name}"
Style="{StaticResource AdminTextBlockStyle}" VerticalAlignment="Bottom"
FontSize="16" />
<!--Status-->
<TextBlock Grid.Row="1" Text="{Binding adminViewModel.StatusDescription}"
Style="{StaticResource AdminTextBlockStyle}" VerticalAlignment="Bottom"
FontSize="13" Margin="0,0,0,1" />
</Grid>
</Border>
<Border Grid.Column="1" HorizontalAlignment="Right" Margin="3">
<Viewbox StretchDirection="DownOnly">
<Image x:Name="ProfilePicture" Source="/Image/ProfilePictures/profile_placeholder.png" />
</Viewbox>
</Border>
</Grid>
</ToggleButton>
</Grid>
<views:OptionPopup x:Name="OptionsMenu" PlacementTarget="{Binding ElementName=AdminPanel}"
Placement="Bottom"
MinWidth="{Binding ActualWidth, ElementName=AdminPanel}"
MaxWidth="{Binding ActualWidth, ElementName=AdminContainer}" />
I fixed it by setting the placement the custom:
<views:OptionPopup x:Name="OptionsMenuPopup"
PlacementTarget="{Binding ElementName=AdminPanel}"
Placement="Custom"
MinWidth="{Binding ActualWidth, ElementName=AdminPanel}"
MaxWidth="{Binding ActualWidth, ElementName=AdminContainer}" />
And in the constructor of my usercontrol in code behind I added
OptionsMenuPopup.CustomPopupPlacementCallback += (Size popupSize, Size targetSize, Point offset) =>
new[] { new CustomPopupPlacement() { Point = new Point(targetSize.Width - popupSize.Width, targetSize.Height) } };
You could use a combination of SystemParameters.PrimaryScreenHeight and the top-left point of the Popup to calculate how much room you have, and set that as the MaxHeight and MaxWidth of the Popup's contents.

Categories

Resources