[WPF自定義控制項庫] 給WPF一個HyperlinkButton

来源:https://www.cnblogs.com/dino623/archive/2019/08/22/WPF_HyperlinkButton.html
-Advertisement-
Play Games

1. 在WPF怎麼在UI上添加超級鏈接 這篇文章的目的是介紹怎麼在WPF里創建自定義的HyperlinkButton控制項。很神奇的,WPF居然連HyperlinkButton都沒有,不過它提供了另一種方式用於在UI上添加超級鏈接: 如果需要在超級鏈接里放圖片或其它東西,代碼如下: 這真是很怪,為什麼 ...


1. 在WPF怎麼在UI上添加超級鏈接

這篇文章的目的是介紹怎麼在WPF里創建自定義的HyperlinkButton控制項。很神奇的,WPF居然連HyperlinkButton都沒有,不過它提供了另一種方式用於在UI上添加超級鏈接:

<TextBlock FontSize="20">           
    <Hyperlink NavigateUri="http://www.google.com" RequestNavigate="Hyperlink_RequestNavigate">
        Click here
    </Hyperlink>
</TextBlock>
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
    Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
    e.Handled = true;
}

如果需要在超級鏈接里放圖片或其它東西,代碼如下:

<TextBlock FontSize="20">           
    <Hyperlink NavigateUri="https://www.microsoft.com"
               RequestNavigate="Hyperlink_RequestNavigate">
        <StackPanel Orientation="Horizontal">
            <Image Source="Microsoft-Logo1.jpg" Height="20" Width="20"/>
            <TextBlock Text="Microsoft"  Margin="4,0,0,0" />
        </StackPanel>
    </Hyperlink>        
</TextBlock>

這真是很怪,為什麼要先有TextBlock然後再有Hyperlink,為什麼TextBlock裡面可以放Image,這真的很難理解。

2. Hyperlink怎麼設置樣式

要給Hyperlink設置樣式也有點難搞,因為在對象樹上Hyperlink毫無存在感,所以也沒辦法使用Blend創建它的Style。

我的做法是用ILSpy拿到它的Style再修改。例如我需要MouseOver狀態下文字不是紅色而是紫色,可以使用下麵的Style:

<Style x:Key="{x:Type Hyperlink}"
       TargetType="{x:Type Hyperlink}">
    <Setter Property="TextElement.Foreground"
            Value="{DynamicResource {x:Static SystemColors.HotTrackBrushKey}}" />
    <Setter Property="Inline.TextDecorations"
            Value="Underline" />
    <Style.Triggers>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding Path=(SystemParameters.HighContrast)}"
                           Value="false" />
                <Condition Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource Self}}"
                           Value="true" />
            </MultiDataTrigger.Conditions>
            <Setter Property="TextElement.Foreground"
                    Value="#FFFF00FF" />
        </MultiDataTrigger>
        <Trigger Property="ContentElement.IsEnabled"
                 Value="False">
            <Setter Property="TextElement.Foreground"
                    Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
        </Trigger>
        <Trigger Property="ContentElement.IsEnabled"
                 Value="True">
            <Setter Property="FrameworkContentElement.Cursor"
                    Value="Hand" />
        </Trigger>
    </Style.Triggers>
</Style>

3. 自定義一個HyperlinkButton

自定義一個HyperlinkButton有什麼好處?因為用起來簡單啊,不需要CodeBehind的代碼,綁定內容和Command都簡單,而且XAML更加簡單直觀。在外觀上,很多人喜歡Hyperlink下麵的橫線在滑鼠MouseOver才顯示,另外如上面圖片所示插入圖片後Hyperlink下麵有一條橫線,這很奇怪但又取消不了。

Silverlight和UWP都很普通地提供了HyperlinkButton。不過在Silverlight中為了顯示MouseOver時出現的下劃線使用了兩層內容,一層用於正常顯示(contentPresenter),另一層用於顯示下劃線(UnderlineTextBlock),如果HyperlinkButton的內容是文本,當MouseOver時UnderlineTextBlock就會顯示UnderlineTextBlock。

<TextBlock x:Name="UnderlineTextBlock"
     Text="{TemplateBinding Content}"
     TextDecorations="Underline"
     Visibility="Collapsed"/>
<ContentPresenter x:Name="contentPresenter"
     Content="{TemplateBinding Content}"/>

但是這樣效果十分差,重疊在一起的文本看上去變得模糊。

而UWP中的HyperlinkButton的下劃線是代碼里寫死的,大概是這樣:

if (VisualTreeHelper.GetChildrenCount(contentPresenter) == 1 && VisualTreeHelper.GetChild(contentPresenter, 0) is TextBlock textBlock)
{
    textBlock.TextDecorations = Text.TextDecorations.Underline;
}

而且它還沒有提供任何方法關閉或修改這個下劃線。我很討厭這種代碼里控制樣式的行為,UI和代碼應該足夠解耦。UWP很多使用代碼控制樣式的行為,通常宣稱理由是為了性能,但Button是整個UI中最不需要性能的部分,畢竟一個UI中不可能有幾百個Button,就算有幾百個HyperlinkButton,現代的UI框架也不可能僅僅因為下劃線就導致性能下降。所以我認為沒必要在代碼里控制下劃線的顯示。

而無論Silverlight還是UWP,只要HyperlinkButton的Content不是純文本就不能顯示下劃線,這應該也算一個功能缺陷。

我在Kino.Toolkit.Wpf里也提供了一個HyperlinkButton,使用方式如下:

<kino:HyperlinkButton Content="Github"
      NavigateUri="https://github.com/DinoChan/Kino.Toolkit.Wpf" />

不僅使用起來簡單,HyperlinkButton的代碼也很簡單。

public Uri NavigateUri
{
    get => GetValue(NavigateUriProperty) as Uri;
    set => SetValue(NavigateUriProperty, value);
}

protected override void OnClick()
{
    base.OnClick();
    if (NavigateUri != null && NavigateUri.IsAbsoluteUri)
    {
        try
        {
            Process.Start(new ProcessStartInfo(NavigateUri.AbsoluteUri));
        }
        catch (Win32Exception)
        {
        }
    }
}

上面是HyperlinkButton的核心代碼,需要一個HyperlinButton被點擊後導航到的NavigateUri屬性,以及在OnClick函數中使用Process.Start在新進程打開目標Uri。關於Process和ProcessStartInfo的具體用法可見本文最後給出的參考鏈接。

XAML的部分基本上照抄Silverlight的HyperlinkButton,不過關於下劃線的處理稍有不同。

<ControlTemplate.Resources>
    <Style TargetType="TextBlock">
        <Style.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ButtonBase}, Path=IsMouseOver}"
                         Value="True">
                <Setter Property="TextDecorations"
                        Value="Underline" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</ControlTemplate.Resources>
<Grid Cursor="{TemplateBinding Cursor}"
      Background="{TemplateBinding Background}">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">
            <VisualState x:Name="Normal" />
            <VisualState x:Name="MouseOver" />
            <VisualState x:Name="Pressed">
                <!--some xaml-->
            </VisualState>
            <VisualState x:Name="Disabled">
                <!--some xaml-->
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <ContentPresenter x:Name="contentPresenter"
                      Content="{TemplateBinding Content}"
                      ContentTemplate="{TemplateBinding ContentTemplate}"
                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                      Margin="{TemplateBinding Padding}">
        <ContentPresenter.Resources>
            <Style TargetType="TextBlock">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ButtonBase}, Path=IsMouseOver}"
                                 Value="True">
                        <Setter Property="TextDecorations"
                                Value="Underline" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ContentPresenter.Resources>
    </ContentPresenter>
</Grid>

上面是HyperlinkButton的DefaultStyle的大致內容。Pressed和Disabled的狀態使用VisualState控制外觀,這部分略過。在ControlTemplate.Resources中添加了一個TextBlock的全局樣式,裡面的DataTrigger設置為當滑鼠進入父節點的HyperlinkButton時TextDecorations變為Underline。運行效果如下:

<kino:HyperlinkButton NavigateUri="https://www.microsoft.com/"
                      Margin="0,16,0,0"
                      FontSize="20">
    <StackPanel Orientation="Horizontal">
        <Image Height="20"
               Width="20"
               Source="/Kino.Toolkit.Wpf.Samples;component/Assets/Images/Microsoft_logo.png" />
        <TextBlock Text="Microsoft"
                   Margin="4,0,0,0"
                   Resources="{x:Null}" />
    </StackPanel>
</kino:HyperlinkButton>

在下麵的ContentPresenter.Resources中也添加了同樣的DataTrigger,這是為了應對下麵這種情況:

<kino:HyperlinkButton Content="Microsoft"
                      NavigateUri="https://www.microsoft.com/"
                      Margin="0,16,0,0"
                      FontSize="20">
    <ButtonBase.ContentTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Image Height="20"
                       Width="20"
                       Source="/Kino.Toolkit.Wpf.Samples;component/Assets/Images/Microsoft_logo.png" />
                <TextBlock Text="Microsoft"
                           Margin="4,0,0,0" />
            </StackPanel>
        </DataTemplate>
    </ButtonBase.ContentTemplate>
</kino:HyperlinkButton>

這裡TextBlock不是HyperlinkButton的邏輯樹上的子元素,或許就是因為這樣它不能應用ControlTemplate.Resources中的TextBlock的全局樣式。

最後記得在最外層的Grid上設置Background:

<Grid Cursor="{TemplateBinding Cursor}" Background="{TemplateBinding Background}">

如果不設置一個透明的background的話,就只有文字部分能捕獲滑鼠點擊事件,這樣HyperlinkButton就會很難點中。(我記得在UWP中就沒有這個問題,UWP的ContentPresenter自帶透明背景)

4. 結語

HyperlinkButton明明很重要但WPF又不提供,幸好自己寫起來也很簡單。

這麼簡單的一個控制項我也能水這麼長的文章,我也很佩服我自己。

5. 參考

Hyperlink Class (System.Windows.Documents) Microsoft Docs

Process Class (System.Diagnostics) Microsoft Docs

ProcessStartInfo Class (System.Diagnostics) Microsoft Docs

6. 源碼

HyperlinkButton.cs at master


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文主要參照https://www.bilibili.com/video/av42364337/?p=4 英文幫助文檔:https://identityserver4.readthedocs.io/en/latest/ 中文幫助文檔:http://www.identityserver.com.cn ...
  • 一、Swagger是什麼 Swagger 是一款RESTFUL介面的、基於YAML、JSON語言的文檔線上自動生成、代碼自動生成的工具。 二、如何在項目中加入Swagger Swagger安裝引用 右鍵Web項目依賴項>管理NuGet程式包>在搜索框輸入"Swashbuckle.AspNetCore ...
  • 本次應用DevExpress和C#語言製作了一個批量添加水印的程式,看界面效果圖: 界面中既可以進行文字水印添加,也可以圖片水印添加,同時還可以對水印的位置進行設置,比較實用! 文字水印的具體添加情況,看圖: 還可以文字的預覽: 整個文字水印的預覽: 同時圖片的水印預覽: 最後顯示下圖片的水印效果: ...
  • 摘要 本文將介紹如何通過VS2019創建Xamarin.Forms應用程式,以及如何進行調試。 前言 本文介紹Xamarin.Froms應用程式的創建和調試。 開發環境 1.Visual Studio 2019 2.Xamarin.Forms 3.6.0.344457 創建 1.打開VS2019,選 ...
  • 初學者經常碰到的,即獲取HTML元素集合,迴圈給元素添加事件。在事件響應函數中(event handler)獲取對應的索引。但每次獲取的都是最後一次迴圈的索引。原因是初學者並未理解JavaScript的閉包特性。 1. <!DOCTYPE HTML> 2. <html> 3. <head> 4. < ...
  • 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制項,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr ...
  • 話不多說,上圖: 整體項目結構如圖所示,我的設計初衷是基於.netCore + DI + Vue 打造一個適合初學者的簡捷開發框架。 架構模型採用基於RESTful API風格的前後臺分離框架,總體分為五層:表示層(前端UI)、交互層、業務層、數據訪問層、數據存儲層。 項目中用到的技術如下圖所示: ...
  • 系統環境: Windows + .Net Framework 4.0 問題描述: C#連接FTP下載文件時,在部分電腦上有異常報錯,在一部分電腦上是正常的;異常報錯的信息:System.InvalidOperationException: The requested FTP command is n ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...