這篇文章的目的是介紹怎麼在WPF裏建立自定義的HyperlinkButton控件。很神奇的,WPF竟然連HyperlinkButton都沒有,不過它提供了另外一種方式用於在UI上添加超級連接:html
<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; }
若是須要在超級連接裏放圖片或其它東西,代碼以下:git
<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,這真的很難理解。github
要給Hyperlink設置樣式也有點難搞,由於在對象樹上Hyperlink毫無存在感,因此也沒辦法使用Blend建立它的Style。windows
個人作法是用ILSpy拿到它的Style再修改。例如我須要MouseOver狀態下文字不是紅色而是紫色,能夠使用下面的Style:api
<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>
自定義一個HyperlinkButton有什麼好處?由於用起來簡單啊,不須要CodeBehind的代碼,綁定內容和Command都簡單,並且XAML更加簡單直觀。在外觀上,不少人喜歡Hyperlink下面的橫線在鼠標MouseOver才顯示,另外如上面圖片所示插入圖片後Hyperlink下面有一條橫線,這很奇怪但又取消不了。框架
Silverlight和UWP都很普通地提供了HyperlinkButton。不過在Silverlight中爲了顯示MouseOver時出現的下劃線使用了兩層內容,一層用於正常顯示(contentPresenter),另外一層用於顯示下劃線(UnderlineTextBlock),若是HyperlinkButton的內容是文本,當MouseOver時UnderlineTextBlock就會顯示UnderlineTextBlock。ide
<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框架也不可能僅僅由於下劃線就致使性能降低。因此我認爲不必在代碼裏控制下劃線的顯示。google
而不管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自帶透明背景)
HyperlinkButton明明很重要但WPF又不提供,幸虧本身寫起來也很簡單。
這麼簡單的一個控件我也能水這麼長的文章,我也很佩服我本身。
Hyperlink Class (System.Windows.Documents) Microsoft Docs
Process Class (System.Diagnostics) Microsoft Docs
ProcessStartInfo Class (System.Diagnostics) Microsoft Docs
原文出處:https://www.cnblogs.com/dino623/p/WPF_HyperlinkButton.html