Show WPF Tooltip if needed - c#

I have a TextBlock inside a limited-size control. If the text is too long to fit into the control, I'd like to show a tooltip with full text. This is a classic behavior you surely know from many apps.
I tried using a Converter to convert TextBlock width into Tooltip's Visibility.
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}">
<TextBlock.ToolTip>
<ToolTip
DataContext="{TemplateBinding Content}"
Visibility="{Binding Converter={StaticResource visConvert}}">
<TextBlock Text="{Binding Text}"></TextBlock>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
The problem is that in the Converter:
public object Convert(object value, ...
'value' is the DataBound item. I'd like the 'value' to be the TextBlock, to observe its Width, and compare it to the GridViewColumn.Width.

I figured it out, the Tooltip has PlacementTarget property that specifies the UI element that has the Tooltip. In case anyone needs it:
<TextBlock Text="{Binding Text}">
<TextBlock.ToolTip>
<ToolTip
DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}"
Visibility="{Binding Converter={StaticResource toolVisConverter}}">
<TextBlock Text="{Binding Text}"/> <!-- tooltip content -->
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
And then write a Converter that converts TextBlock to Visibility (based on TextBlock width).

Ok, so why do it the hard XAML-only way? This works:
<TextBlock Text="{Binding Text}"
IsMouseDirectlyOverChanged="TextBlock_IsMouseDirectlyOverChanged" >
<TextBlock.ToolTip>
<ToolTip Visibility="Collapsed">
<TextBlock Text="{Binding Text}"></TextBlock>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
in Control.xaml.cs:
private void TextBlock_IsMouseDirectlyOverChanged(object sender, DependencyPropertyChangedEventArgs e)
{
bool isMouseOver = (bool)e.NewValue;
if (!isMouseOver)
return;
TextBlock textBlock = (TextBlock)sender;
bool needed = textBlock.ActualWidth >
(this.listView.View as GridView).Columns[2].ActualWidth;
((ToolTip)textBlock.ToolTip).Visibility =
needed ? Visibility.Visible : Visibility.Collapsed;
}

I would think you have to look at a ControlTemplate trigger to solve this problem. Unfortunately ControlTemplate triggers always compare with a specific value, not less than or greater than. You can make it appear e.g. if the Width = 100, not Width < 100.

Related

Visual C#: Can't find correct relative width for listbox

So I have a listbox and I'm trying to use a stackpanel inside as an item with a border. Now I want every item to be the same width as my listbox, which is anchored to the sides of the window. I found how to set the width relative to the parent but for some reason it turns out to be wider. Can't seem to figure out why. Picture below code of how it looks.
<ListBox x:Name="listBoxSecrets" Margin="10,107,10,10" Background="{x:Null}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Orange" CornerRadius="2,2,2,2" BorderThickness="2,2,2,2">
<StackPanel Background="White"
Width="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type ListBox}},
Path=ActualWidth}"
>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Totp}" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ListBox.ActualWidth is too much for ListBoxItems. But ListBoxItems will use all available width if ListBox has HorizontalContentAlignment set to "Stretch":
<ListBox HorizontalContentAlignment="Stretch"

How to create WPF ComboBox with two aligned columns (and auto width for the first one)?

Turn this
]1
Into this
Full description:
I was able to do it with this nasty hack (aggregating max column width needed):
// result.Type == typeof(List<MethodInfo>)
_returnTypeColumnWidth = result.Max(info => new FormattedText(info.ReturnType.Name, CultureInfo.CurrentCulture, WindowsFlowDirection.LeftToRight, new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), FontSize, MediaBrushes.Black).Width);
and binding first control's (in a ComboBox.ItemTemplate) Width to it.
The question is: how to do it with MVVM?
My idea was to get MinWidth from a parent's property (which will be updated with Width changes):
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type sd:InvokeMethodDesigner}}, Path=ReturnTypeColumnWidth, Mode=OneWayToSource}"
MinWidth="{Binding RelativeSource={RelativeSource AncestorType={x:Type sd:InvokeMethodDesigner}}, Path=ReturnTypeColumnWidth, Mode=OneWay}"
Margin="0,0,4,0"
TextAlignment="Right" Text="{Binding ReturnType.Name}" />
<TextBlock Text="{Binding Path=., Converter={StaticResource MethodInfoNameConverter}}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
C#:
public double ReturnTypeColumnWidth
{
get { return _returnTypeColumnWidth; }
set
{
if(Double.IsNaN(value) || (value <= _returnTypeColumnWidth))
return;
_returnTypeColumnWidth = value;
RaisePropertyChanged("ReturnTypeColumnWidth");
}
}
But the ReturnTypeColumnWidth_set() is only called once, during TextBlock initialization (value is NaN).
You can get different Grids to share column or row auto-sizing among themselves using the attached properties Grid.IsSharedSizeScope and Grid.SharedSizeGroup. This does exactly what you're trying to do, but somebody else already wrote the code. If you had to write it yourself, I would recommend doing it with attached properties much like this -- or if you're in a real hurry, maybe even kludge it in the code behind. As Will notes in comments, this logic belongs entirely to the view. The less the viewmodel knows or cares about display column widths, the better.
<ComboBox
...
Grid.IsSharedSizeScope="True"
...
>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto"
Grid.SharedSizeGroup="ReturnTypeNameColumn"
/>
<ColumnDefinition
Width="*"
/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,0,4,0"
TextAlignment="Right"
Text="{Binding ReturnType.Name}"
/>
<TextBlock
Grid.Column="1"
Text="{Binding Path=., Converter={StaticResource MethodInfoNameConverter}}"
/>
</Grid>
</DataTemplate>
<ComboBox.ItemTemplate>
</ComboBox>
You might have to set ItemsPanel and put Grid.IsSharedSizeScope on the StackPanel you use for the items host, but try this first and see how it goes.

WPF ToolTip Placement Target returns null

I have this code:
<DataTemplate>
<Border>
<Border.ToolTip>
<ToolTip IsEnabled="True"
Placement="Right">
<ToolTip.VerticalOffset>
<MultiBinding Converter="{StaticResource OffsetConverter}"
ConverterParameter="Vertical">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding.Bindings>
</MultiBinding>
</ToolTip.VerticalOffset>
<TextBlock Margin="0"
Padding="0"
TextAlignment="Left"
TextWrapping="Wrap"
MaxWidth="200"
Text="{Binding Description}" FontStyle="Italic">
</TextBlock>
</ToolTip>
</Border.ToolTip>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="0">
<Image Height="16" Width="16" Style="{StaticResource AutoCompletionImageStyle}" Margin="0"/>
<Label Content="{Binding DisplayText}" Margin="0" Padding="0"/>
</StackPanel>
</Border>
</DataTemplate>
And this converter :
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
AutoCompletionViewModel.OffsetType offsetType = (AutoCompletionViewModel.OffsetType)Enum.Parse(typeof(AutoCompletionViewModel.OffsetType), parameter.ToString());
ToolTip tooltip = values[0] as ToolTip;
Border border = tooltip.PlacementTarget as Border;
double aCalculatedOffset = 0.0;
return aCalculatedOffset;
}
I need to calculate the offset of the tooltip based on some properties of the Border. The tooltip appears relative to the border and manually changing the offset works as expected. However when I try to access the PlacementTarget property of the ToolTip in the converter it is null...
So, although the ToolTip is a direct child of the Border and should have it as a placement target, in the code it is null. Any idea what I am missing here?
I also tried passing the border with <Binding RelativeSource="{RelativeSource AncestorType={x:Type Border}}"/> to no avail.
I am really confused since, if the border is not the logical or visual parent of the ToolTip how does the ToolTip places itself correctly?!
I managed to find a workaround :
<DataTemplate>
<Border ToolTipOpening="OnAutoCompletionBorderToolTipOpening">
<Border.ToolTip>
<ToolTip>
<ToolTip.Style>
<Style>
<EventSetter Event="ToolTip.Opened" Handler="ToolTipOpenedHandler" />
</Style>
</ToolTip.Style>
</ToolTip>
</Border.ToolTip>
</Border>
</DataTemplate>
Code behind:
private void ToolTipOpenedHandler(object sender, RoutedEventArgs e)
{
ToolTip toolTip = (ToolTip)sender;
UIElement target = toolTip.PlacementTarget;
var rect = toolTip.PlacementRectangle;
}
The target here is not null, rather the Border...
I am pretty sure this is some WPF bug. Hopefully someone will be helped by this post.

Dynamically add TextBlock to StackPanel

So I'm developing a Windows Phone 8 app in C# using MVVM pattern. I have a screen where the layout needs to be like this:
TextBlock
Image
TextBlock
TextBlock
If the Dynamic list of buttons.count is > 1 insert a TextBlock here
Dynamic list of buttons
TextBlock
TextBlock
So I've been trying to set up this in xaml code, and it looks like this:
<StackPanel Orientation="Vertical">
<TextBlock Margin="20,0,20,0" TextWrapping="Wrap" Foreground="#c8d75a" Text="{Binding Title, Mode=TwoWay}" Height="46"></TextBlock>
<Image Margin="20,0,20,0" Source="{Binding ImageLink}" Height="200"></Image>
<TextBlock Margin="20,0,20,0" TextWrapping="Wrap" Foreground="#7e9d83" Text="{Binding subTitle, Mode=TwoWay}" Height="46"></TextBlock>
<TextBlock Margin="20,0,20,0" TextWrapping="Wrap" Foreground="#7e9d83" Text="{Binding Description, Mode=TwoWay}" Height="46"></TextBlock>
<ItemsControl ItemsSource="{Binding RelatedLinks}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Margin="20,0,20,0" Foreground="#c8d75a" Text="{Binding Text, Mode=TwoWay}" Height="46">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cm:ActionMessage MethodName="OpenLink">
<cm:Parameter Value="{Binding Href}"></cm:Parameter>
</cm:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Margin="20,0,20,0" TextWrapping="Wrap" Foreground="#7e9d83" Text="{Binding Creator, Mode=TwoWay}" Height="46"></TextBlock>
<TextBlock Margin="20,0,20,0" TextWrapping="Wrap" Foreground="#7e9d83" Text="{Binding PubDate, Mode=TwoWay}" Height="46"></TextBlock>
Now everything is working as it should except the part on my list that says "If the Dynamic list of buttons.count is > 1 insert a TextBlock here".
Let me try to explain that further. My {Binding RelatedLinks} is a bind to a ObservableCollection<RelatedLink> where RelatedLink is an object with two strings Href and Text.
So if my ObservableCollection of RelatedLinks is bigger then 1 i want to have a title text placed above this ItemsControl list. How can i achieve this?
ObservableCollection implements INotifyPropertyChanged and notifies about changes to Count, so you can bind to it. To show or hide a TextBlock, you would change its Visibility like this:
<TextBlock
Visibility="{Binding RelatedLinks.Count, Converter={StaticResource CountToVisibilityConverter}}"
... />
What is left is to write the CountToVisibilityConverter and add it to resources. Its Convert method would be something like:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value > 1 ? Visibility.Visible: Visibility.Collapsed;
}
An alternative approach would be to add a property bool TextVisible to your ViewModel and update it every time you update the ObservableCollection, then use a standard BoolToVisibilityConverter.

Show/hide content on the WP7 pushpin

I have a puspin ContentTemplate:
<my:Pushpin.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text1}"/>
<TextBlock Text="{Binding Text2}"/>
</DataTemplate>
</my:Pushpin.ContentTemplate>
How could I show and hide it clickking on the pushpin (Could be a lot of pushpins on the map and I need to show the content of the clicked one)?
You are already binding text to your Pushpin. You can bind visibility to it as well. I am assuming here that each Pushpin is binding to a seperate object.
<my:Pushpin.ContentTemplate>
<DataTemplate>
<Grid Visibility="{Binding IsSelected, Converter={StaticResource BoolToVisibilityConverter}">
<TextBlock Text="{Binding Text1}"/>
<TextBlock Text="{Binding Text2}"/>
</Grid>
</DataTemplate>
</my:Pushpin.ContentTemplate>
If you don't know how to use converters, then you can search for them and find all kinds of answers that should be helpful. I'll include one here for convenience

Categories