Search code examples
wpftooltipcode-behind

Creating a custom databound tooltip in codebehind


How would I create something like this in a code behind?

<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
            <Setter Property="OverridesDefaultStyle" Value="true"/>
            <Setter Property="HasDropShadow" Value="True"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ToolTip">
                        <Border CornerRadius="5" HorizontalAlignment="Center" VerticalAlignment="Top" Padding="1" BorderThickness="1,1,1,1">
                            <Border.Background>
                                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                    <GradientStop Color="#F7D073" Offset="0"/>
                                    <GradientStop Color="#F1A62F" Offset="1"/>
                                </LinearGradientBrush>
                            </Border.Background>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Border HorizontalAlignment="Stretch" BorderThickness="0,0,0,1" BorderBrush="Black" Margin="5" Grid.ColumnSpan="2">
                                    <TextBlock FontSize="14" TextAlignment="Left" Text="{TemplateBinding Content}"/>
                                </Border>
                                <TextBlock Grid.Column="0" Grid.Row="1" Margin="10,0,5,0">Column1:</TextBlock>
                                <TextBlock Grid.Column="1" Grid.Row="1" FontWeight="Bold" Text="{Binding Column1}"  TextAlignment="Left" />
                                <TextBlock Grid.Column="0" Grid.Row="2" Margin="10,0,5,0">Column2:</TextBlock>
                                <TextBlock Grid.Column="1" Grid.Row="2" FontWeight="Bold" Text="{Binding Column2}" TextAlignment="Left" />
                                <TextBlock Grid.Column="0" Grid.Row="3" Margin="10,0,5,0">Column3:</TextBlock>
                                <TextBlock Grid.Column="1" Grid.Row="3" FontWeight="Bold" Text="{Binding Column3}" TextAlignment="Left" />
                                <TextBlock Grid.Column="0" Grid.Row="4" Margin="10,0,5,0">Column4:</TextBlock>
                                <TextBlock Grid.Column="1" Grid.Row="4" FontWeight="Bold" Text="{Binding Column4}" TextAlignment="Left" />
                                <TextBlock Grid.Column="0" Grid.Row="5" Margin="10,0,5,0">Column5:</TextBlock>
                                <TextBlock Grid.Column="1" Grid.Row="5" FontWeight="Bold" Text="{Binding Column5}" TextAlignment="Left" />
                                <TextBlock Grid.Column="0" Grid.Row="6" Margin="10,0,5,0">ColumnX:</TextBlock>
                                <TextBlock Grid.Column="1" Grid.Row="6" FontWeight="Bold" Text="{Binding ColumnX}" TextAlignment="Left" />
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

The idea is to pass in a DataTable to the control with an unknown number of columns and be able to build a tooltip with however many columns there are.

The above code was used to build a very specific case but now I would like to make this as generic as possible however my knowledge of WPF codebehind is not where it needs to be to creat something like this.


Solution

  • So I could not do this with the grid inside the tooltip but i was able to do it with stackpanels

    the solutions is a multistaged approch. First to override the default style of a ToolTip

            var style = new Style {TargetType = typeof (ToolTip)};
            style.Setters.Add(new Setter {Property = TemplateProperty, Value = GetToolTip(dataTable)});
            style.Setters.Add(new Setter{Property = OverridesDefaultStyleProperty, Value = true});
            style.Setters.Add(new Setter{Property = System.Windows.Controls.ToolTip.HasDropShadowProperty, Value = true});
            Resources.Add(typeof(ToolTip), style);
    

    Then to create a template for the TemplateProperty setter the contents of the GetToolTip() function look like this

       private ControlTemplate GetToolTip(DataTable dt) {
    
            //Create Template where all factories will be assigned to
            var templateTT = new ControlTemplate(typeof (ToolTip));
    
            //Main border and background of the ToolTip
            var borderFactory = new FrameworkElementFactory(typeof (Border));
            borderFactory.SetValue(Border.CornerRadiusProperty, new CornerRadius(5));
            borderFactory.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center);
            borderFactory.SetValue(VerticalAlignmentProperty, VerticalAlignment.Top);
            borderFactory.SetValue(PaddingProperty, new Thickness(1, 1, 1, 1));
            borderFactory.SetValue(BorderThicknessProperty, new Thickness(1,1,1,1));
    
            //Brush for the background
            var borderBrush = new LinearGradientBrush {EndPoint = new Point(.5, 1), StartPoint = new Point(.5, 0)};
            borderBrush.GradientStops.Add(new GradientStop(Color.FromArgb(255,247,208,115),0));
            borderBrush.GradientStops.Add(new GradientStop(Color.FromArgb(255, 241, 166, 47), .5));
    
            //Assign the brush to the backgfound property of the border
            borderFactory.SetValue(BackgroundProperty, borderBrush);
    
            //Main Vertical StackPanel that will contain all the horizontal stackpanels
            var verticalStackFactory = new FrameworkElementFactory(typeof (StackPanel));
            verticalStackFactory.SetValue(StackPanel.OrientationProperty, Orientation.Vertical);
    
            foreach (DataColumn item in dt.Columns) {
    
                //ID column is to be igoned and not shown on the tooltip
                if(item.ColumnName.Equals("ID")) continue;
    
                //Name column will be used as the "Header" of the tooltip
                if (item.ColumnName.Equals("Name")) {
                    var headerStackFactory = new FrameworkElementFactory(typeof(StackPanel));
                    headerStackFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
    
                    //border of the header (to create the underline)
                    var headerBorderBrush = new FrameworkElementFactory(typeof (Border));
                    headerBorderBrush.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
                    headerBorderBrush.SetValue(BorderThicknessProperty, new Thickness(0, 0, 0, 1));
                    headerBorderBrush.SetValue(BorderBrushProperty, new SolidColorBrush(Colors.Black));
                    headerBorderBrush.SetValue(MarginProperty, new Thickness(5));
    
                    //Text of the header bound to the Name column
                    var headerTextBox = new FrameworkElementFactory(typeof (TextBlock));
                    headerTextBox.SetValue(FontSizeProperty, 14.0);
                    headerTextBox.SetValue(TextBlock.TextAlignmentProperty, TextAlignment.Left);
                    headerTextBox.SetValue(ForegroundProperty, new SolidColorBrush(Colors.Black));
                    headerTextBox.SetBinding(TextBlock.TextProperty, new Binding("Name"));
    
                    headerBorderBrush.AppendChild(headerTextBox);
                    headerStackFactory.AppendChild(headerBorderBrush);
                    verticalStackFactory.AppendChild(headerStackFactory);
                }
                else {
                    //Horizontal Stack Panel
                    var horizontalStackFactory = new FrameworkElementFactory(typeof (StackPanel));
                    horizontalStackFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
    
                    //Add the TextBlock Label
                    var factoryLabel = new FrameworkElementFactory(typeof (TextBlock));
                    factoryLabel.SetValue(MarginProperty, new Thickness(10, 0, 5, 0));
                    factoryLabel.SetValue(ForegroundProperty, new SolidColorBrush(Colors.Black));
                    factoryLabel.SetValue(TextBlock.TextProperty, string.Format("{0}: ", item.ColumnName.Replace("_", " ")));
                    horizontalStackFactory.AppendChild(factoryLabel);
    
                    //Add the TextBlock Value bound to the column name
                    var factoryText = new FrameworkElementFactory(typeof (TextBlock));
                    factoryText.SetValue(FontWeightProperty, FontWeights.Bold);
                    factoryText.SetValue(ForegroundProperty, new SolidColorBrush(Colors.Black));
                    factoryText.SetValue(TextBlock.TextAlignmentProperty, TextAlignment.Left);
                    factoryText.SetBinding(TextBlock.TextProperty, new Binding(item.ColumnName));
                    horizontalStackFactory.AppendChild(factoryText);
    
                    verticalStackFactory.AppendChild(horizontalStackFactory);
                }
            }
    
            borderFactory.AppendChild(verticalStackFactory);
            templateTT.VisualTree = borderFactory;
            return templateTT;
        }
    

    Create the template for the combobox item

            var template = new DataTemplate();
            var textBlockFactory = new FrameworkElementFactory(typeof (TextBlock));
            textBlockFactory.SetValue(ToolTipService.ShowDurationProperty, 60000);
            textBlockFactory.SetValue(ToolTipService.InitialShowDelayProperty, 0);
            textBlockFactory.SetBinding(TextBlock.TextProperty, new Binding("Name"));
            textBlockFactory.SetBinding(ToolTipProperty, new Binding("Name"));
            template.VisualTree = textBlockFactory;
    
            InsurancePlanMaster.ItemTemplate = template;
    

    This allowed me to create a custom ComboBox bound to a datatable with a custom tool tip bound to the other rows in the table.