1. 程式人生 > >【轉】WPF自定義控制元件與樣式(9)-樹控制元件TreeView與選單Menu-ContextMenu

【轉】WPF自定義控制元件與樣式(9)-樹控制元件TreeView與選單Menu-ContextMenu

一.前言

  申明:WPF自定義控制元件與樣式是一個系列文章,前後是有些關聯的,但大多是按照由簡到繁的順序逐步釋出的等。

  本文主要內容:

  • 選單Menu的自定義樣式;
  • 右鍵選單ContextMenu的自定義樣式;
  • 樹控制元件TreeView的自定義樣式,及右鍵選單實現。

二.選單Menu的自定義樣式

  自定義選單樣式的效果圖:

  Menu和ContextMenu樣式本身很簡單,他們最主要的部分就是MenuItem,MenuItem中包含的內容比較多,如圖示、選中狀態、二級選單、二級選單的指標、快捷鍵等。 使用了字型圖示定義選單項MenuItem樣式程式碼:  

<!--選單項MenuItem樣式FIconMenuItem-->
    <Style x:Key="FIconMenuItem" TargetType="{x:Type MenuItem}">
        <Setter Property="BorderBrush" Value="{StaticResource MenuBorderBrush}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Background" Value="
{StaticResource MenuBackground}"/> <Setter Property="Foreground" Value="{StaticResource MenuForeground}"/> <Setter Property="FontSize" Value="{StaticResource FontSize}"/> <Setter Property="Height" Value="28"/> <Setter Property="Width" Value="Auto"/> <Setter Property="Margin" Value="1"/> <Setter Property="local:ControlAttachProperty.FIconSize" Value="22"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="
{x:Type MenuItem}"> <!--Item--> <Border x:Name="border" Background="Transparent" Height="{TemplateBinding Height}" Opacity="1"> <Grid VerticalAlignment="Center" Margin="{TemplateBinding Margin}"> <Grid.ColumnDefinitions> <ColumnDefinition x:Name="icon_col" MaxWidth="35" SharedSizeGroup="MenuItemIconColumnGroup"/> <ColumnDefinition Width="Auto" SharedSizeGroup="MenuTextColumnGroup"/> <ColumnDefinition Width="Auto" SharedSizeGroup="MenuItemIGTColumnGroup"/> <ColumnDefinition Width="16" x:Name="arrow_col" SharedSizeGroup="MenumItemArrow"/> </Grid.ColumnDefinitions> <!--icon--> <TextBlock x:Name="PART_Icon" Text="{TemplateBinding Icon}" Foreground="{TemplateBinding Foreground}" Margin="5,1,1,1" FontSize="{TemplateBinding local:ControlAttachProperty.FIconSize}" Style="{StaticResource FIcon}"/> <!--Header--> <ContentPresenter Grid.Column="1" x:Name="txtHeader" Margin="3,1,5,1" MinWidth="90" RecognizesAccessKey="True" VerticalAlignment="Center" ContentSource="Header"/> <!--快捷鍵 InputGestureText 暫不支援你了 --> <TextBlock Grid.Column="2" Margin="3,1,3,1" x:Name="IGTHost" Text="{TemplateBinding InputGestureText}" FontSize="{TemplateBinding FontSize}" VerticalAlignment="Center" Visibility="Visible" Foreground="{TemplateBinding Foreground}" /> <!--右指標--> <TextBlock x:Name="PART_Arrow" Grid.Column="3" Text="&#xe605;" Foreground="{TemplateBinding Foreground}" FontSize="14" Style="{StaticResource FIcon}"/> <!--淡出子集選單容器--> <Popup x:Name="SubMenuPopup" AllowsTransparency="true" IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}" Placement="Bottom" Focusable="false" VerticalOffset="0" PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}"> <Border Background="{TemplateBinding Background}" CornerRadius="0" Margin="5" Effect="{StaticResource DefaultDropShadow}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Grid x:Name="SubMenu" Grid.IsSharedSizeScope="True"> <StackPanel Margin="0" IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/> </Grid> </Border> </Popup> </Grid> </Border> <!--觸發器--> <ControlTemplate.Triggers> <!--TopLevelHeader:第一級選單(有子選單)--> <Trigger Property="Role" Value="TopLevelHeader"> <Setter Property="Visibility" Value="Collapsed" TargetName="PART_Arrow"/> <Setter Property="Visibility" Value="Collapsed" TargetName="IGTHost"/> <Setter Property="Margin" Value="5,1,1,1" TargetName="PART_Icon"/> <Setter Property="Margin" Value="1,1,6,1" TargetName="txtHeader"/> <Setter Property="MinWidth" Value="10" TargetName="txtHeader"/> <Setter Property="Width" Value="0" TargetName="arrow_col"/> </Trigger> <!--TopLevelItem 第一級選單(無子級)--> <Trigger Property="Role" Value="TopLevelItem"> <Setter Property="Visibility" Value="Collapsed" TargetName="PART_Arrow"/> <Setter Property="Visibility" Value="Collapsed" TargetName="IGTHost"/> <Setter Property="Margin" Value="5,1,1,1" TargetName="PART_Icon"/> <Setter Property="Margin" Value="1,1,6,1" TargetName="txtHeader"/> <Setter Property="MinWidth" Value="10" TargetName="txtHeader"/> <Setter Property="Width" Value="0" TargetName="arrow_col"/> </Trigger> <!--SubmenuHeader:子選單,有子選單--> <Trigger Property="Role" Value="SubmenuHeader"> <Setter Property="Visibility" Value="Visible" TargetName="PART_Arrow"/> <Setter Property="Placement" Value="Right" TargetName="SubMenuPopup"/> </Trigger> <!--SubMenuItem:子選單,無子級--> <Trigger Property="Role" Value="SubMenuItem"> <Setter Property="Visibility" Value="Collapsed" TargetName="PART_Arrow"/> </Trigger> <!--選中狀態,優先順序將高於Icon--> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="PART_Icon" Value="&#xe62a;" Property="Text"></Setter> <Setter TargetName="PART_Icon" Value="18" Property="FontSize"></Setter> <Setter TargetName="PART_Icon" Value="{StaticResource CheckedForeground}" Property="Foreground"></Setter> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter TargetName="border" Value="{StaticResource DisableOpacity}" Property="Opacity"></Setter> </Trigger> <!--高亮狀態--> <Trigger Property="IsHighlighted" Value="true"> <Setter Property="Background" TargetName="border" Value="{StaticResource MenuMouseOverBackground}"></Setter> <Setter Property="Foreground" Value="{StaticResource MenuMouseOverForeground}"></Setter> </Trigger> <Trigger Property="IsPressed" Value="true"> <Setter Property="Background" TargetName="border" Value="{StaticResource MenuPressedBackground}"></Setter> <Setter Property="Foreground" Value="{StaticResource MenuPressedForeground}"></Setter> </Trigger> <!--子選單開啟狀態--> <Trigger Property="IsSubmenuOpen" Value="true" > <Setter TargetName="PART_Arrow" Value="{StaticResource CheckedForeground}" Property="Foreground"></Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--基於FIconMenuItem的預設樣式,提供Header模板--> <Style x:Key="DefaultMenuItem" TargetType="{x:Type MenuItem}" BasedOn="{StaticResource FIconMenuItem}"> <Setter Property="HeaderTemplate"> <Setter.Value> <DataTemplate> <TextBlock x:Name="txtHeader" FontSize="{Binding FontSize,RelativeSource={RelativeSource AncestorType={x:Type MenuItem},Mode=FindAncestor}}" HorizontalAlignment="Stretch" Margin="3,1,5,1" Text="{Binding Header,RelativeSource={RelativeSource AncestorType={x:Type MenuItem},Mode=FindAncestor}}" VerticalAlignment="Center" Foreground="{Binding Foreground,RelativeSource={RelativeSource AncestorType={x:Type MenuItem},Mode=FindAncestor}}"/> </DataTemplate> </Setter.Value> </Setter> </Style>

Menu樣式:  

<!--預設Menu樣式-->
    <Style x:Key="DefaultMenu" TargetType="{x:Type Menu}">
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="RenderOptions.ClearTypeHint" Value="Enabled" />
        <Setter Property="TextOptions.TextFormattingMode" Value="Ideal" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="ItemContainerStyle" Value="{StaticResource DefaultMenuItem}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Menu}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
                            Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}"
                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                        <ItemsPresenter Margin="0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

示例程式碼:  

<MenuItem Header="幫助(H)"  InputGestureText="Ctrl+H" Icon="&#xe643;" >
                    <MenuItem Header="設定" Icon="&#xE633;"/>
                    <MenuItem Icon="&#xE639;" Header="外掛管理" />
                    <MenuItem Icon="&#xe655;"  Header="使用者管理" />
                    <MenuItem Icon="&#xe64a;" Header="修改密碼" />
                    <MenuItem Icon="" Header="線上更新" />
                    <Separator Style="{StaticResource HorizontalSeparatorStyle}"/>
                    <MenuItem Icon="&#xe657;" Header="問題反饋" />
                    <MenuItem Icon="&#xe61e;" Header="技術支援" />
                    <MenuItem Icon="&#xe60e;" Header="幫助" />
                    <MenuItem Icon="&#xe635;" Header="關於" />
                </MenuItem>

三.右鍵選單ContextMenu的自定義樣式

有了第二節的MenuItem樣式,ContextMenu的樣式很簡單:  

<!--預設右鍵選單ContextMenu樣式-->
    <Style x:Key="DefaultContextMenu" TargetType="{x:Type ContextMenu}">
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="RenderOptions.ClearTypeHint" Value="Enabled" />
        <Setter Property="TextOptions.TextFormattingMode" Value="Ideal" />
        <Setter Property="BorderBrush" Value="{StaticResource MenuBorderBrush}"/>
        <Setter Property="Background" Value="{StaticResource MenuBackground}"/>
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Foreground" Value="{StaticResource MenuForeground}"/>
        <Setter Property="OverridesDefaultStyle" Value="True" />
        <Setter Property="Grid.IsSharedSizeScope" Value="True" />
        <Setter Property="HasDropShadow" Value="True" />
        <Setter Property="ItemContainerStyle" Value="{StaticResource DefaultMenuItem}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ContextMenu}">
                    <Grid>
                        <Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" Margin="5"
                                BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"
                                Padding="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle"
                                                Grid.IsSharedSizeScope="True" Margin="0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                                KeyboardNavigation.TabNavigation="Cycle" />
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="HasDropShadow" Value="True">
                            <Setter TargetName="Border" Property="Effect" Value="{StaticResource DefaultDropShadow}">
                            </Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

實現一個文字操作(剪下、複製、貼上)的樣式:  

 <!--文字操作右鍵選單-->
    <ContextMenu x:Key="TextBoxContextMenu" Style="{StaticResource DefaultContextMenu}">
        <MenuItem Command="ApplicationCommands.Cut" Icon="&#xe662;" Style="{DynamicResource DefaultMenuItem}" />
        <MenuItem Command="ApplicationCommands.Copy" Icon="&#xe661;" Style="{DynamicResource DefaultMenuItem}" />
        <MenuItem Command="ApplicationCommands.Paste" Icon="&#xe663;" Style="{DynamicResource DefaultMenuItem}" />
    </ContextMenu>

效果圖:

 

 

四.樹控制元件TreeView的自定義樣式

4.1TreeView基本樣式

TreeView的樣式比較簡單,相比ListBox,主要多了層級關係,節點的展開、收縮。效果圖:

樣式定義中預設是開啟虛擬化,以支援大資料,資料不多時最好關閉。樣式程式碼:

<!--TreeViewItem預設樣式-->
    <Style  x:Key="DefaultTreeViewItem" TargetType="{x:Type TreeViewItem}">
        <Setter Property="MinHeight" Value="25" />
        <Setter Property="Foreground" Value="{StaticResource TextForeground}" />
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="Margin" Value="0" />
        <Setter Property="local:ControlAttachProperty.FIconSize" Value="19"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TreeViewItem}">
                    <StackPanel>
                        <Border x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"
                                MinHeight="{TemplateBinding MinHeight}" UseLayoutRounding="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <!--多層級間隔,暫緩-->
                            <!--<Grid Margin="{Binding Converter={StaticResource LengthConverter}, RelativeSource={x:Static RelativeSource.TemplatedParent}}"-->
                            <Grid Margin="{TemplateBinding Margin}" VerticalAlignment="Stretch">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition MinWidth="18" Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <!--展開收縮按鈕-->
                                <ToggleButton x:Name="ExpanderBtn" 
                                              IsChecked="{Binding Path=IsExpanded, RelativeSource={x:Static RelativeSource.TemplatedParent}, Mode=TwoWay}"
                                              ClickMode="Press" >
                                    <ToggleButton.Template>
                                        <ControlTemplate TargetType="ToggleButton">
                                            <Border>
                                                <ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                                            </Border>
                                        </ControlTemplate>
                                    </ToggleButton.Template>
                                    <ToggleButton.Content>
                                        <TextBlock x:Name="ExpanderIcon"  Foreground="{TemplateBinding Foreground}" Text="&#xe62c;" Style="{StaticResource FIcon}"
                                                   FontSize="{TemplateBinding local:ControlAttachProperty.FIconSize}" />
                                    </ToggleButton.Content>
                                </ToggleButton>
                                <!--內容-->
                                <ContentPresenter x:Name="PART_Header" Grid.Column="1" ContentSource="Header"
                                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                            </Grid>
                        </Border>
                        <ItemsPresenter Margin="18,0,0,0" x:Name="ItemsHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="False">
                            <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
                        </Trigger>
                        <Trigger Property="IsExpanded" Value="True">
                            <Setter TargetName="ExpanderIcon" Property="Text" Value="&#xe62d;" />
                        </Trigger>
                        <Trigger Property="HasItems" Value="False">
                            <Setter TargetName="ExpanderIcon" Property="Visibility" Value="Hidden" />
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="{StaticResource ItemMouseOverBackground}" />
                            <Setter Property="Foreground" Value="{StaticResource ItemMouseOverForeground}" />
                        </Trigger>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="{StaticResource ItemSelectedBackground}" />
                            <Setter Property="Foreground" Value="{StaticResource ItemSelectedForeground}" />
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="True" />
                                <Condition Property="Selector.IsSelectionActive" Value="True" />
                            </MultiTrigger.Conditions>
                            <Setter Property="Background" Value="{StaticResource ItemSelectedBackground}" />
                            <Setter Property="Foreground" Value="{StaticResource ItemSelectedForeground}" />
                        </MultiTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!--TreeView樣式-->
    <Style x:Key="DefaultTreeView" TargetType="{x:Type TreeView}">
        <Setter Property="ScrollViewer.CanContentScroll" Value="True" />
        <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"></Setter>
        <Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling" />
        <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
        <Setter Property="Background" Value="{StaticResource ItemsContentBackground}"/>
        <Setter Property="ItemContainerStyle" Value="{StaticResource DefaultTreeViewItem}"></Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel IsItemsHost="True" IsVirtualizing="True" VirtualizationMode="Recycling" Margin="0"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>

4.2 TreeView的右鍵選單實現

  TreeView支援右鍵操作應該是比較常見的需求,實現很簡單,效果演示:

示例程式碼:  

<HierarchicalDataTemplate x:Key="ItemNode"  DataType="{x:Type local:NodeX}" ItemsSource="{Binding Nodes}">
            <StackPanel Orientation="Horizontal" Height="28">
                <core:FImage Source="{Binding Icon}" Width="22" Height="22"></core:FImage>
                <TextBlock Text="{Binding Name}" FontSize="13" VerticalAlignment="Center" Margin="3,0,0,0"></TextBlock>
            </StackPanel>
        </HierarchicalDataTemplate>
            
<TreeView Width="250"  Margin="3" x:Name="tree1" ItemTemplate="{StaticResource ItemNode}">
                <TreeView.ItemContainerStyle>
                    <Style BasedOn="{StaticResource DefaultTreeViewItem}" TargetType="{x:Type TreeViewItem}">
                        <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
                    </Style>
                </TreeView.ItemContainerStyle>
                <TreeView.ContextMenu>
                    <ContextMenu>
                        <MenuItem  Icon="&#xe662;" Header="展開" Click="MenuItem_OnClick"  Style="{DynamicResource DefaultMenuItem}" />
                        <MenuItem  Icon="&#xe661;" Header="剪下" Style="{DynamicResource DefaultMenuItem}" />
                        <MenuItem  Icon="&#xe663;" Header="賦值" Style="{DynamicResource DefaultMenuItem}" />
                    </ContextMenu>
                </TreeView.ContextMenu>
            </TreeView>

後臺C#程式碼:  

private void TreeViewItem_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            var treeViewItem = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
            if (treeViewItem != null)
            {
                treeViewItem.Focus();
                e.Handled = true;
            }
        }

        static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
        {
            while (source != null && source.GetType() != typeof(T))
                source = VisualTreeHelper.GetParent(source);

            return source;
        }

        private void MenuItem_OnClick(object sender, RoutedEventArgs e)
        {
            var item = this.tree1.SelectedItem as NodeX;
            if (item != null)
            {
                MessageBoxX.Info(item.Name.ToString());
            }
        }

 

原文地址:https://www.cnblogs.com/anding/p/4996614.html