WPF中TreeView的+-號和連線style的一種實現

来源:https://www.cnblogs.com/lzablog/archive/2018/11/09/TreeViewItemStyle.html
-Advertisement-
Play Games

定義WPF中TreeViewItem的一種Style,提供一種處理ItemsControl中類似方案的思路。 ...


最近又開始跟WPF打交道,項目裡面用到了TreeView這個控制項。然後需要有一個連線的外觀就像是這樣

二話不說,百度了一下,找到一個實現, 通道

把代碼拷貝到項目裡面,跑了一下,看上去還不錯。但是這哥們的實現裡面有一個缺陷。我們可以看到每個節點的最後的子節點前面的豎線,繪製方式跟其他的節點是有區別的,跟上圖顯示的一樣。

拷貝的代碼,在樹中添加新的節點的時候,之前最後節點的"|_"這種風格的線在新節點插入後不會動態更新,還是會保持這種風格。這種風格在不需要改變節點的時候還是可以的,而需要增刪節點的話,那麼  就不合適了。

繼續在網上找了找沒有現成的解決方案,後來想了一下,可以試試在給一個節點添加子節點的時候,將所有子節點都刪除然後再重新添加進去這種方式來更新UI。但我沒去嘗試這個方案是否真的可以。額,不管是否可行,我都不太喜歡這個方案,所以就只能自己做了。

那麼從這裡開始描述整個實現過程。


首先是問題的根源,一組節點的最後一個節點前面的豎線的繪製方式是需要區別於其他的節點。然後這個是由於它的位置所決定的,而跟它本身的屬性是沒有一點關係的,我們也沒有什麼直接的方式來得到這個信息,能做的無非就像上面那個哥們那樣,去跑一遍index之類的方法來判定是否是最後一個節點。

根據這個邏輯,我們可以發現,單單處理TreeViewItem這個類的Style是沒有用的,或者說是有缺陷的。

那麼怎麼辦呢?

TreeViewItem本身是ItemsControl,TreeView本身也是,那麼它的繪製方式就很明顯了,無非是ItemsTemplate決定了Item的數據外觀,ItemsPanel決定瞭如何去組織Items。

我們要做的就是在繪製Items的時候,把豎線放到裡面就行了,這樣我們就有了豎線,而且我們在遍歷的時候是知道每個節點的位置信息的,這樣最後一個節點的繪製信息我們是能拿到的。

橫線的話,我們有兩個選擇,一個是也在ItemsPanel裡面去繪製;或者我們把這根線放到TreeViewItem的Style裡面。我選擇了後者,原因是,我們的“+”是在TreeViewItem裡面的。而“+”在橫線的前面。

ItemsPanel的話,我們重寫一下StackPanel就足夠了。

這樣方案基本就確定了:

1. 重寫StackPanel來繪製Item前面的豎線

2. 重寫TreeViewItem的Style來繪製“+-”號和橫線

 

首先們準備數據模型,來調試UI

public class ItemViewModel
{
    public string Name { get; set; }
    public IList<ItemViewModel> Children { get; set; }
}

TreeView的基本設置

<TreeView x:Name="treeView">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

TreeView和TreeViewItem的預設模板,直接從msdn拷貝,通道

只需要在OnRender方法裡面在繪製Item的時候在前面畫一根線就行了,主要是處理一下最後一個節點前的線,只繪製1/2即可。但這麼算的話,會有一個問題,如果節點自己有子節點的話,那麼子節點的高度是會算在當前節點的,這樣一來豎線的高度是錯的,我做了一個設定,就是在子節點的內容沒有換行的情況下,所有的子節點的高度都是一樣的(實際上,基本情況也就是這樣)。這樣一來:1. 沒有子節點那麼就是1/2的子節點高度;如果有那麼就是RenderSize.Height / Items.Count / 2這種。

protected override void OnRender(DrawingContext dc)
{
    base.OnRender(dc);
    if (Children.Count > 0)
    {
        TreeViewItem child;
        Point point;
        Pen pen = new Pen(LineBrush, LineThiness);
        Point startPoint = new Point(Offset,0), endPoint = new Point(Offset,0);
        for (int i = 0; i < Children.Count - 1; i++)
        {
            child = Children[i] as TreeViewItem;
            point = child.TranslatePoint(new Point(), this);
            startPoint.Y = point.Y;
            endPoint.Y = point.Y + child.RenderSize.Height;
            dc.DrawLine(pen, startPoint, endPoint);
        }
        child = Children[Children.Count - 1] as TreeViewItem;
        point = child.TranslatePoint(new Point(), this);
        startPoint.Y = point.Y;
        if (!child.IsExpanded || child.Items == null || child.Items.Count < 1)
            endPoint.Y = point.Y + child.RenderSize.Height / 2;
        else
            endPoint.Y = point.Y + child.RenderSize.Height / child.Items.Count / 2;
        dc.DrawLine(pen, startPoint, endPoint);
    }
}

然後再新建一個TreeViewItem的Style,裡面畫個“+” "-"號再加一根橫線就好了

        <Style x:Key="customToggleButtonStyle" TargetType="{x:Type ToggleButton}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToggleButton}">
                        <Grid Background="White">
                            <Rectangle  Fill="{x:Null}" Stroke="Black" StrokeThickness="1"/>
                            <Rectangle Height="1"  Fill="Black" VerticalAlignment="Center" />
                            <Rectangle Width="1" Fill="Black" HorizontalAlignment="Center"  x:Name="line" Visibility="Collapsed"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="False">
                                <Setter Property="Visibility" Value="Visible" TargetName="line"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

設置一下TreeViewItem中的ItemsPanel引用我們定義的Panel,就可以了,當然還有一些細節要處理,不過我就不細說了,我已經沒耐心說下去了,所有的代碼都在下麵,寫這篇文章主要是想寫了,嗯,就是這樣。

<Window x:Class="TreeViewStyleDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TreeViewStyleDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <!--Control colors.-->
        <Color x:Key="WindowColor">#FFE8EDF9</Color>
        <Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
        <Color x:Key="ContentAreaColorDark">#FF7381F9</Color>
        <Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
        <Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
        <Color x:Key="DisabledForegroundColor">#FF888888</Color>
        <Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
        <Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
        <Color x:Key="ControlLightColor">White</Color>
        <Color x:Key="ControlMediumColor">#FF7381F9</Color>
        <Color x:Key="ControlDarkColor">#FF211AA9</Color>
        <Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
        <Color x:Key="ControlPressedColor">#FF211AA9</Color>
        <Color x:Key="GlyphColor">#FF444444</Color>
        <Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>
        <!--Border colors-->
        <Color x:Key="BorderLightColor">#FFCCCCCC</Color>
        <Color x:Key="BorderMediumColor">#FF888888</Color>
        <Color x:Key="BorderDarkColor">#FF444444</Color>
        <Color x:Key="PressedBorderLightColor">#FF888888</Color>
        <Color x:Key="PressedBorderDarkColor">#FF444444</Color>
        <Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
        <Color x:Key="DisabledBorderDarkColor">#FF888888</Color>
        <Color x:Key="DefaultBorderBrushDarkColor">Black</Color>
        <!--Control-specific resources.-->
        <Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
        <Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
        <Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>
        <Color x:Key="NavButtonFrameColor">#FF3843C4</Color>
        <LinearGradientBrush x:Key="MenuPopupBrush" EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" />
            <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="0.5" />
            <GradientStop Color="{DynamicResource ControlLightColor}" Offset="1" />
        </LinearGradientBrush>
        <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" StartPoint="0,0" EndPoint="1,0">
            <LinearGradientBrush.GradientStops>
                <GradientStopCollection>
                    <GradientStop Color="#000000FF" Offset="0" />
                    <GradientStop Color="#600000FF" Offset="0.4" />
                    <GradientStop Color="#600000FF" Offset="0.6" />
                    <GradientStop Color="#000000FF" Offset="1" />
                </GradientStopCollection>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
        <Style x:Key="{x:Type TreeView}" TargetType="TreeView">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TreeView">
                        <Border Name="Border" CornerRadius="1" BorderThickness="1">
                            <Border.BorderBrush>
                                <SolidColorBrush Color="{DynamicResource BorderMediumColor}" />
                            </Border.BorderBrush>
                            <Border.Background>
                                <SolidColorBrush Color="{DynamicResource ControlLightColor}" />
                            </Border.Background>
                            <ScrollViewer Focusable="False" CanContentScroll="False" Padding="4">
                                <ItemsPresenter />
                            </ScrollViewer>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
            <Setter Property="Focusable" Value="False" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ToggleButton">
                        <Grid Width="15" Height="13" Background="Transparent">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CheckStates">
                                    <VisualState x:Name="Checked">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Collapsed">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}" />
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Expanded">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}" />
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Unchecked" />
                                    <VisualState x:Name="Indeterminate" />
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Path x:Name="Collapsed" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="1,1,1,1" Data="M 4 0 L 8 4 L 4 8 Z">
                                <Path.Fill>
                                    <SolidColorBrush Color="{DynamicResource GlyphColor}" />
                                </Path.Fill>
                            </Path>
                            <Path x:Name="Expanded" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="1,1,1,1" Data="M 0 4 L 8 4 L 4 8 Z" Visibility="Hidden">
                                <Path.Fill>
                                    <SolidColorBrush Color="{DynamicResource GlyphColor}" />
                                </Path.Fill>
                            </Path>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="customToggleButtonStyle" TargetType="{x:Type ToggleButton}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToggleButton}">
                        <Grid Background="White">
                            <Rectangle  Fill="{x:Null}" Stroke="Black" StrokeThickness="1"/>
                            <Rectangle Height="1"  Fill="Black" VerticalAlignment="Center" />
                            <Rectangle Width="1" Fill="Black" HorizontalAlignment="Center"  x:Name="line" Visibility="Collapsed"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="False">
                                <Setter Property="Visibility" Value="Visible" TargetName="line"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="TreeViewItemFocusVisual">
            <Setter Property="Control.Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Border>
                            <Rectangle Margin="0,0,0,0" StrokeThickness="5" Stroke="Black" StrokeDashArray="1 2" Opacity="0" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="HorizontalContentAlignment" Value="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
            <Setter Property="VerticalContentAlignment" Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
            <Setter Property="Padding" Value="1,0,0,0" />
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
            <Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}" />
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <local:CustomPanel LineBrush ="Black" Background="LightYellow" SnapsToDevicePixels="True" Orientation="Vertical" LineThiness=".5" Offset="9.5" IsItemsHost="True"/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TreeViewItem}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition MinWidth="19" Width="Auto" />
                                <ColumnDefinition Width="Auto" />
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition />
                            </Grid.RowDefinitions>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="SelectionStates">
                                    <VisualState x:Name="Selected">
                                        <Storyboard>
                                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd" Storyboard.TargetProperty="(Panel.Background).  (SolidColorBrush.Color)" >
                                                <EasingColorKeyFrame KeyTime="0" Value="{StaticResource SelectedBackgroundColor}" />
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Unselected" />
                                    <VisualState x:Name="SelectedInactive">
                                        <Storyboard>
                                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd" Storyboard.TargetProperty="(Panel.Background).  (SolidColorBrush.Color)">
                                                <EasingColorKeyFrame KeyTime="0" Value="{StaticResource SelectedUnfocusedColor}" />
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="ExpansionStates">
                                    <VisualState x:Name="Expanded">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="ItemsHost">
                                                <DiscreteObjectKeyFrame 
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 相信大家在寫項目的時候遇到過,資料庫里的時間戳類型(時間類型),後臺獲取了,返回前臺,但是前臺的格式不是你想要的格式。例如: 這樣是有毫秒,我在網上找了個簡單又方便的方法: 1、導入JSTL的jar包 2、在jsp頁面加入 3、本來我們輸出是 現在我們輸出是 結果: 最後,如果我們要的格式只要年月日 ...
  • 函數屬性 python中的函數是一種對象,它有屬於對象的屬性。除此之外,函數還可以自定義自己的屬性。註意,屬性是和對象相關的,和作用域無關。 自定義屬性 自定義函數自己的屬性方式很簡單。假設函數名稱為myfunc,那麼為這個函數添加一個屬性var1: 那麼這個屬性var1就像是全局變數一樣被訪問、修 ...
  • 1.利用Filter來過濾的時候大都是Http請求和Http響應,在doFilter()方法中,參數類是ServletRequest和ServletResponse ,使用的時候一般需要強制轉換為HttpServletRequest和HttpServletResponse 。針對這個問題,可以仿照G ...
  • ########模板層######## 模板層其實就是templates文件夾里的html文件 其實這裡的每個html不是真正意義的上html代碼,只有經過模板渲染過後才算的上真正的html頁面。 一、模板語言(變數,深度查詢,過濾器,標簽) 1、變數 在django模板里通過{{ name }} ... ...
  • 一、三個內置函數 1、issubclass(a, b) 判斷a類是否是b類的子類 1 class Foo: 2 pass 3 4 class Zi(Foo): 5 pass 6 7 class Sun(Zi): 8 pass 9 print(issubclass(Zi,Foo)) #True 10 ...
  • 元組 列表非常適合用於存儲在程式運行期間可能變化的數據集。列表是可以修改的,而不可變的列表被稱為元組。元組使用圓括弧來標識。定義元組後可以使用索引來訪問元素。 例如: 遍歷元組中的所有值 像列表一樣,可以使用for迴圈來遍歷元組中的所有值 修改元組變數 雖然不能修改元組的元素,但是可以給元組的變數賦 ...
  • " 【.NET Core項目實戰 統一認證平臺】開篇及目錄索引 " 這篇文章,我們將從Ocelot的中間件源碼分析,目前Ocelot已經實現那些功能,還有那些功能在我們實際項目中暫時還未實現,如果我們要使用這些功能,應該如何改造等方面來說明。 一、Ocelot源碼解讀 在使用一個組件前,最好我們要了 ...
  • 猜想1:Random.Next()產生的隨機數不會有重覆。 猜想2:大量級執行Random.Next(int i)分佈在各個數值上的概率是均勻的。 寫作時間:2018-11-09 本文只代表本人的見解,可能存在錯誤,僅用於技術交流。如果你喜歡該文,可以掃下麵的二維碼打賞我(打賞敬請備註“博客園打賞” ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...