Custom WPF tooltip - wpf

I want to create a WPF tooltip containing a label for the header of the tooltip and then a textblock containing more detailed text. I've created the following style in a resource dictionary:
<Style x:Key="AppToolTip"
TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<StackPanel>
<Label Content="{TemplateBinding Content}" FontWeight="Bold" Background="Blue" Foreground="White">
</Label>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow" Width="200">
</TextBlock>
</StackPanel>
</ControlTemplate>
</Setter.Value></Setter>
</Style>
And can successfully apply this style to a button like so and have the tooltip header appear:
<Button.ToolTip>
<ToolTip Style="{DynamicResource PalletToolTip}">
<Binding Source="{x:Static ResStrings.New}"/>
</ToolTip>
</Button.ToolTip>
What i'm stuck on is how can i set the content of the extra descriptive text from the usage above ? I'm already data binding to the Content property when showing the tooltip header.
Anyone who's read Adam Nathan's WPF Unleashed book will recognise that i'm using his example tooltip XAML but in his case, he's used hard coded strings for the content of the label and textblock. I want to create something that's more reusable and hence want to use data binding to achieve the same effect.

I would inherit a HeaderedToolTip class from ToolTip and add a Header property. I would specify the template for that control much as you've done. Then I would be able to use it like so:
<Button>
<Button.ToolTip>
<HeaderedToolTip Header="My Title" Content="My Content"/>
</Button.ToolTip>
</Button>
Or, with bindings:
<Button>
<Button.ToolTip>
<HeaderedToolTip Header="{Binding ToolTipTitle}" Content="{Binding ToolTipText}"/>
</Button.ToolTip>
</Button>

You can use an object or ViewModel that contains all the necessary properties you need in the tooltip.
class MyToolTipViewModel : INotifyPropertyChanged
{
public string Header
{
get{ return mHeader;}
set{ mHeader = value; RaisePropertyChanged("Header"); }
}
public void RaisePropertyChanged(string aProperty)
{
// .. implementation of INotifyPropertyChanged
}
}
then you can set the tolltip directly on an instance of this class.
myButton.ToolTip = new MyToolTipViewModel();
now after that, your tooltip will just show the full qualified name of the ViewModel class.
What you need now is a DataTemplate, which tells WPF how to convert the class into visual object.
<DataTemplate DataType="{x:Type MyToolTipViewModel}">
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
The DataTemplate need to be placed in the resource tree. In the resource section of a higher level object or directly on the application or window resources level.
Hope that helps.

Related

WPF Command Binding ItemsControl in Styles

I have a style in the Textboxstyles.xaml as following
<Style x:Key="EmptyItemsControlUsabilityDashboard2017Style" TargetType="ItemsControl">
<Style.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Image Height="12" Width="12" Source="/YoLo;component/Resources/Images/link.png" Margin="0,3,0,0" />
<TextBlock x:Name="EmptyCollectionTextBox" Text="{x:Static UsabilityDashboard2017Loc:DashboardUsability2017Resource.lblNumNotDefined}"
Style="{StaticResource UsabilityDashboard2017TextBoxStyle}"
HorizontalAlignment="Center"
Margin="5,25,0,25"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
and I have used it inside another Xaml file as following
<ItemsControl ItemsSource="{Binding YoLoViewModelsCollection}" Name="YoLoViewModelsItemSource" Style="{StaticResource EmptyItemsControlUsabilityDashboard2017Style}">
Now it shows a text box that the this collection is empty but how can I set command bindings on the text block named "EmptyCollectionTextBox" inside the style that user user click it executes a command?
I have already seen the custom commands but somehow they are not working.
There's actually a lot of stuff wrong with this code. First of all I have no idea what that trigger is supposed to be doing, it looks like the controltemplate will only be set if there are no elements in the list?
Secondly, it looks like you're trying to represent each element in the collection with an image and text, all in an ItemsControl. You don't do that by templating the entire control, you do it by templating the ItemTemplate. And you use a DataTemplate, not a ControlTemplate:
Now going back to your actual question, you want notification whenever the TextBlock is clicked on. There are a multitude of different ways to do this, but for this case you may as well replace that TextBlock with a Button, and then override the Template with a ControlTemplate that represents it as a TextBlock. This gives you the best of both worlds: from the GUI's perspective it's really still a TextBlock, but you still get all the button click notifications and Command handler etc:
<Image />
<Button Command="{Binding ClickedCommand}" Cursor="Hand">
<Button.Template>
<ControlTemplate>
<TextBlock Text="Text Binding Goes Here" />
</ControlTemplate>
</Button.Template>
</Button>
This particular example assumes that the items in your collection have a command handler called "ClickedCommand". In practice your handler might reside in the parent class (e.g. the main window's view model), in which case you'd need to give your main window a x:Name (e.g. "_this") and bind to that instead, passing the item in as the CommandParameter so it knows which one was clicked:
<Button Command="{Binding ElementName=_this, Path=DataContext.ClickedCommand}" CommandParameter="{Binding}" Cursor="Hand">
And then your main view model has a handler that looks something like this:
public ICommand ClickedCommand { get { return new RelayCommand<YourCollectionItemType>(OnClicked); } }
private void OnClicked(YourCollectionItemType item)
{
}

Binding TabControl.ItemsSource to ViewModels whose View's are a TabItem

I need some TabItems to have a customized Header.
For example, given the following (working) XAML:
<TabControl>
<TabItem>
<TabItem.Header>
<Button>Header 1</Button>
</TabItem.Header>
<Label>Content 1</Label>
</TabItem>
<TabItem>
<TabItem.Header>
<Label>Header 2</Label>
</TabItem.Header>
<Grid>
<TextBlock>Content 2</TextBlock>
</Grid>
</TabItem>
</TabControl>
I would like to extract the tab items into their own Views + ViewModels. The TabItem's View should still be a TabItem, so that I can configure the Header per tab item instead of setting TabControl.ItemTemplate and using a DataTemplateSelector to achieve different headers per tab item.
At the same time I'd need to be able to bind the selected tab item view model to a property ActiveItem. => The underlying view-model for the TabControl is a Conductor.Collection.OneActive<T> (only the selected tab should be activated).
If there's an alternative to using TabItem as view-type, but still achieving the Header and Content to be specified in the same view, it would be acceptable, too.
You should be able to achieve this by binding a TabControlViewModel to the TabControl, and that VM should have an ObservableCollection of TabViewModels (maybe a base class or interface). You would bind your collection of TabViewModels to the TabControl's ItemsSource. Here is my implementation, but using the Telerik TabControl (should be same for MS):
<telerik:RadTabControl x:Name="RadTabControl"
Grid.Row="0"
Align="Justify"
ContentTemplateSelector="{StaticResource LoggerDataTemplateSelector}"
IsContentPreserved="True"
IsDefaultItemSelected="True"
ItemsSource="{Binding LogHistory}"
SupressSelectedContentTemplateReapplying="False">
<telerik:RadTabControl.ItemContainerStyle>
<!-- Allow IsSelected to be bound to view models-->
<Style BasedOn="{StaticResource RadTabItemStyle}"
TargetType="{x:Type telerik:RadTabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</telerik:RadTabControl.ItemContainerStyle>
<telerik:RadTabControl.ItemTemplate>
<!-- Define what is shown in the header -->
<DataTemplate>
<Grid Height="30">
<TextBlock VerticalAlignment="Center"
Text="{Binding Title}" />
</Grid>
</DataTemplate>
</telerik:RadTabControl.ItemTemplate>
</telerik:RadTabControl>
Be aware, MS doesn't have something like IsContentPreserved, so switching tabs that have a lot of data to show will be rather timely. There are also a couple other properties not in MS TabControl, however the important properties should all be there. In this case, your TabViewModel should have a IsSelected property and Title property.

How to show Master-Detail Informations in a collapsable ListView

I have a class that describes a certain task. This "task"-Class has a list of substeps.
What I wanted to do is to show these Informations in a ListView where the task-description should be used as the Groupheader and the substeps as the details
Also I wanted to be able to collapse and expand these groups.
Here is what I tried so far (simplified, but you'll get the idea hopefully):
public class Task : INotifyPropertyChanged
{
public string Description;
public ObservableCollection<String> substeps;
...
}
in the Viewmodel :
Task t = new Task();
t.Description = "Task1";
t.substeps.Add("substep 1");
t.substeps.Add("substep 2");
...
Tasks = new CollectionViewSource { Source = TaskList }; //TaskList is just a ObservableCollection<Task>
Tasks.GroupDescriptions.Add(new PropertyGroupDescription("Description"));
in xaml:
<s:SurfaceListBox Width="500" Height="1000" ItemsSource="{Binding TaskList.View}">
<s:SurfaceListBox.Resources>
<Style x:Key="ContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Description}" IsExpanded="True">
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</s:SurfaceListBox.Resources>
<s:SurfaceListBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource ContainerStyle}"/>
</s:SurfaceListBox.GroupStyle>
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding substeps}"/>
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
</s:SurfaceListBox>
The Result is a Listbox with my collapsed item. If I expand it, instead of each step I only see "(Listing)"
Do I have to build a class with the substeps and the description in it, and group by the description?
Try this:
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource={Binding substeps} />
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
Your problem is that the string binding to the ObservableCollection will just do a ToString() on that collection. You need iterate through the collection and display each item. By using the ItemsControl as I have done, you can also DataTemplate each subtask as you see fit.
ControlTemplate Binding
The Expander header binding will not work because it is inside a ControlTemplate which is inside the Style. The DataContext for the control template will not be your ViewModel but the control (i.e. SurfaceListBox) itself. Similar question is here.
There are two ways you can fix this.
1.Use DataTemplate
<s:SurfaceListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Description}"
IsExpanded="True" >
<ItemsControl ItemsSource={Binding substeps} />
</Expander >
</DataTemplate>
</s:SurfaceListBox.ItemTemplate>
2.Use TemplatedParent binding
<Expander Header="{Binding Content.Description, , RelativeSource={RelativeSource TemplatedParent}}"
IsExpanded="True">
I personally recommend option 1.

Getting the ListItem of a Databound list in Silverlight

in my Silverlight 4 Application, I have an observable list of a user defined class
ObservableCollection<MyClass> myList;
public class MyClass
{
public string Name { get; set; }
public string Value { get; set; }
}
I display this list in a ListBox, using databinding and a Template for the ListBoxItems:
<ListBox x:Name="ListBoxCharacteristics" Background="{x:Null}" HorizontalAlignment="Left" >
<!-- DataTemplate to display the content -->
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="StackPanelBorder" Orientation="Horizontal" HorizontalAlignment="Left">
<TextBox x:Name="TextBoxCharacteristicName" Style="{StaticResource InputTextBox}" Text="{Binding Name}" />
<TextBox x:Name="TextBoxSep" Style="{StaticResource ReadOnlyTextBox}" Text="=" />
<TextBox x:Name="TextBoxValue" Style="{StaticResource InputTextBox}" Text="{Binding Value}" LostFocus="FormulaTextBox_LostFocus" TextChanged="Formula_TextChanged"/>
<Button x:Name="ButtonCheck" Style="{StaticResource RoundWebdingButton}" Content="s" Click="ButtonCheck_Click" />
<Button x:Name="ButtonAccept" Style="{StaticResource RoundWebdingButton}" Content="a" Click="ButtonAccept_Click" />
<Button x:Name="ButtonRemove" Style="{StaticResource RoundWebdingButton}" Content="r" Click="ButtonRemove_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The user can change the values in the textboxes and can use the buttons to verify the input, accept it (write it to the underlying model) or remove the entry. To manipulate the underlying model, I need to access the associated item (that is displayed in the listboxitem, where the user has clicked the button).
One idea to get the item was to use the SelectedItem - Property, which will contain the wanted instance of MyClass. Problem is, that clicking on a button or a textbox doesn't select the containing ListBoxItem. The user would have to manually select the Listboxitem first by clicking somewhere at the item, where no textbox or button is displayed. Otherwise, SelectedItem will be null.
I could get the TextBoxCharacteristicName TextBox via the parent object of the Button, but as the user shall be able to change this content too, I would be unable to get the correct item using this property as identifier.
Any other idea, how to find out, which MyClass-instance is the one that is displayed in the corresponding ListBoxItem?
Thanks in advance,
Frank
Found it! The Button has a property "DataContext", that contains the MyClass-object I was looking for!

WPF - HeaderStringFormat Doesn't work in Expander

I can't seem to find the magic combination to make the HeaderStringFormat work for a WPF Expander.
Here are all the things I've tried:
<Expander Header="{Binding Path=MyProperty, StringFormat=Stuff: ({0})}" >
<TextBlock Text="Some Content" />
</Expander>
<Expander HeaderStringFormat="{}Stuff ({0})" Header="{Binding Path=MyProperty}">
<TextBlock Text="Some More Content" />
</Expander>
<Expander HeaderStringFormat="{}Stuff ({0:0})" Header="{Binding Path=MyProperty}">
<TextBlock Text="Even More Content" />
</Expander>
The only way I can get a formatted string to work correctly in my code is to do this:
<Expander>
<Expander.Header>
<TextBlock Text="{Binding Path=MyProperty, StringFormat=Stuff: ({0})}" />
</Expander.Header>
<Expander.Content>
A Expander with working header
</Expander.Content>
</Expander>
What am I doing wrong?
First thing to note is this:
If you set the HeaderTemplate or
HeaderTemplateSelector property of a
HeaderedContentControl, the
HeaderStringFormat property is
ignored.
MSDN
There are quite a few gotchas like this in WPF to watch out for. You didn't show that in your example, but just keep it in mind. However, I don't think this is your problem.
Second thing to note is that this isn't the same as:
String.Format("My string value is: {0}", myValue");
HeaderedContentControl and HeaderStringFormat are used specifically for classes that implement IFormattable. HederStringFormat formats the header, and ContentStringFormat formats the content. The value of either property is the format that gets passed to your classes implementation if IFormattable.ToString. You can read the full example on MSDN. But here is the gist of how to make it work.
public class MyTestClass : IFormattable
{
#region IFormattable Members
public string ToString(string format, IFormatProvider formatProvider)
{
if(format == "n")
{
return "This is my formatted string";
}
else
{
return "this is my non-formatted string";
}
}
#endregion
}
<Style TargetType="{x:Type TabItem}">
<Setter Property="HeaderStringFormat" Value="n" />
<Setter Property="ContentStringFormat" Value="" />
</Style>
<TabControl>
<TabItem Header="{Binding Content, RelativeSource={RelativeSource Self}}">
<local:MyTestClass />
</TabItem>
</TabControl>
This TabItem will now display "This is my formatted string" in the header, and the content will be "this is my non-formatted string".
There a couple things to keep in mind. Typically these properties would be used only in an HeaderedItemsControl context. The HeaderStringFormat would not be bound in this way, and instead will have the default binding provided by the ItemContainer of the HeaderedItemsControl. For instance if you set the ItemsSource property of the TabItem, then it will automatically wire up the header and the content binding for you, and all you have to do is supply the formatting value you want.
Last, but not least, I was able to get everything working properly with a GroupBox and TabItem, but not so much luck with an expander and I'm not sure why. The expander handles the ContentStringFormat properly, but not the HeaderContentStringFormat. This is suprising considering that the both inherit from HeaderContentControl.

Resources