Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

Add HyperlinkButton

Open robloo opened this issue 2 years ago • 14 comments

Is your feature request related to a problem? Please describe.

Other XAML frameworks have a HyperlinkButton (not to be confused with an inline hyperlink text element inside of a text control). This control is useful to:

  1. Simplify the common use case of a hyperlink button
  2. Provide a standard look-and-feel for hyperlinks in the framework
  3. Provide a cross-platform way of navigating to Uri like webpages.
  4. Automatically manages a change in text color after a link is clicked the first time.

There are additional opportunities here as well. The HyperlinkButton could have a new NavigateUri property added (like UWP). If a NavigateUri is set, then the HyperlinkButton is pressed, that Uri would be opened in a platform-specific way. This would greatly simplify opening something like a webpage on each platform.

Describe the solution you'd like

Add a new HyperlinkButton control derived from button with a standardized style.

Describe alternatives you've considered

I've seen often times in the past developers are told to just create their own custom Button style and use that instead.

Additional context Add any other context or screenshots about the feature request here.

robloo avatar Feb 18 '22 02:02 robloo

I've seen often times in the past developers are told to just create their own custom Button style and use that instead.

Yes.

Snipped from one of my projects:

<Style Selector="Button.link">
  <Setter Property="Foreground" Value="Blue" />
  <Setter Property="Padding" Value="0" />
  <Setter Property="Cursor" Value="Hand" />
  <Setter Property="BorderThickness" Value="0" />
  <Setter Property="Background" Value="Transparent" />
  <Setter Property="Template">
    <ControlTemplate>
      <ContentPresenter Content="{TemplateBinding Content}">
        <ContentPresenter.Styles>
          <Style Selector="TextBlock">
             <Setter Property="Foreground" Value="{TemplateBinding Foreground}"/>
             <Setter Property="FontSize" Value="{TemplateBinding FontSize}"/>
             <Setter Property="TextDecorations" Value="Underline"/>
          </Style>
        </ContentPresenter.Styles>
      </ContentPresenter>
    </ControlTemplate>
  </Setter>
</Style>

It can be easily extended to include some kind of icons as well or richer content.

maxkatz6 avatar Feb 18 '22 04:02 maxkatz6

The HyperlinkButton could have a new NavigateUri property added (like UWP). If a NavigateUri is set, then the HyperlinkButton is pressed, that Uri would be opened in a platform-specific way. This would greatly simplify opening something like a webpage on each platform.

It might be controversial, but I don't agree NavigateUri is useful. It's very business-requirements specific. Sometimes you don't need to open link in the browser, but open link inside of your app. Or execute simple command. Click handling should be delegated to the developers, who know better what is needed in their application. But at this point we have another question "why it's called hyperlink button then? only because of visual style?". And yes, in my app it's about visual style, and only in one place it actually opens link in the browser (in other places it opens helper popup).

On other hand, though, there is at least one more scenario when basic Button style is not enough - special visual state when button was already pressed once. Of course it can be easily implemented in the application, but some might consider it as a an important part of hyperlink button visual.

maxkatz6 avatar Feb 18 '22 04:02 maxkatz6

Can't we have a build in command to navigate to a Hyperlink, which accepts an URI as CommandParameter?

This would make it easy to use and also keep the flexibility.

timunie avatar Feb 18 '22 04:02 timunie

You don't have to use NavigateUri if you don't want to. In apps I've worked on its quite helpful for quickly going to online to docs, user agreements, etc.

The already pressed state is a good point I thought about a while ago but forgot to add. I'll update, thanks.

robloo avatar Feb 18 '22 04:02 robloo

Whoops, forgot about this one when I said I didn't have plans for more controls from WinUI in Avalonia. @maxkatz6 I'm assuming the core team will generally frown on a PR to implement this? I can hold off for a while after other discussions are resolved.

robloo avatar Apr 11 '22 04:04 robloo

If this control will be more useful than "button with a style", I am good with it. Especially if most of the code can be reused from the Button control. Tbh I don't remember if WPF/UWP HyperlinkButton is actually better than that.

But I still think it's not really necessary control, so I wouldn't put any high priority on it.

maxkatz6 avatar Apr 11 '22 05:04 maxkatz6

@maxkatz6 Well, it will also open hyperlinks if a Navigate URL is provided and have a new pseudoclass :navigated or something to change the color after a link is clicked. Basically 1-4 in the description. It will be very lightweight though and derive from button.

Which reminds me: I made changes to Button a while ago for DropDownButton that make inheriting for SplitButton make even more sense. I've also kept searching on why Microsoft didn't derive SplitButton from Button and the best guess is to flatten the Visual Tree / controls hierarchy for performance. This means I might change SplitButton to derive from Button as well -- so you would get your preference there after all.

robloo avatar Apr 11 '22 05:04 robloo

I've decided not to add this control for 11.0. I still plan to add it at some point in the future -- probably for 12.0 in a year or so. But time is limited and this isn't critical. This control can be added without any breaking changes at any point in the future.

robloo avatar Oct 29 '22 16:10 robloo

I think we can add also the help-wanted label, so anyone having some free time can implement this feature.

timunie avatar Oct 30 '22 17:10 timunie

I will take a look at the uwp implementation and see if I can finish this in v11

rabbitism avatar Oct 31 '22 14:10 rabbitism

@rabbitism I had several ideas I was going to include as well. Also the FluentAvalonia one is a good base. Actually, the existence of the Fluent Avalonia one and this not being a blocker for other controls is why I deprioritized this myself.

Aside from the 4 original points above (with cross platform navigation link in Fluent Avalonia with mobile added) I was going to add a property that could specify if the link was already navigated or not.

That property would be automatically set to true after the first click. It would then set a 'navigated' pseudoclass to change the colors in the template. I never figured out good names for the property and pseudoclass though.

robloo avatar Oct 31 '22 14:10 robloo

visited would be my favorite. But I'm happy with whatever name is used. The question for me is how to handle this in virtualized controls like ListBox and what happens if several buttons share the same URL and you want to mark all as visited if one of these buttons where clicked. I personally think this should be a property in the ViewModel, so the developer has full control over it.

timunie avatar Oct 31 '22 15:10 timunie

Well, the point of making it a separate property was to allow apps direct control over showing the hyperlink as having been navigated to or not. I suppose an additional property to control this is needed to disable marking the link as visited on click if the app is truly managing it all.

robloo avatar Oct 31 '22 15:10 robloo

Here are some quick notes based on latest discussion.

// Same as UWP/WinUI
public Uri NavigateUri { get; set; }

// Gets or sets a value indicating whether the navigate URI has already been visited.
// Remarks: When true, the hyperlink will change visual state to indicate the link has already been visited.
public bool IsUriVisited { get; set; }

// This property isn't needed. Auto-navigate will always be enabled if a NavigateUri is provided.
// Otherwise, no navigation will be done on click. However, the app may still set IsUriVisited to change pseudoclasses
// public bool IsAutoNavigateEnabled { get; set; }

// Pseudoclasses
":visited"

Note that this enables both automatic URI navigation and changing the control style accordingly -- or -- don't provide a NavigateUri and control everything through a view model with the IsUriVisited property. Best of both worlds.

robloo avatar Oct 31 '22 16:10 robloo

I have added a new direct property: bool SetVisitedOnClick, to control whether the state should be managed by the control itself or by user.

rabbitism avatar Nov 06 '22 14:11 rabbitism

I wonder if that property should be styled property in order to manage it for all controls

timunie avatar Nov 06 '22 15:11 timunie

I wonder if that property should be styled property in order to manage it for all controls

Hmmm, let me think about it.

rabbitism avatar Nov 06 '22 15:11 rabbitism

I have added a new direct property: bool SetVisitedOnClick, to control whether the state should be managed by the control itself or by user.

Is the property itself really necessary though? If state should be managed by the user then most of the time no NavigateUri will be provided and instead Click will be used externally. That's why I decided there was no need for the 'AutoNavigateEnabled' property above. I realize controlling style state is slightly different but in practical use-cases I'm not sure it will be used much.

I would also make this a styled property though if it stays. "DirectProperty" in my mind is more for content -- and items lists -- that are never used in the styling system. We might want to set this new property in ControlTheme and style classes.

Finally, for bool properties I try very hard to maintain the "Is" convention. IsVisitedAutoSet or IsVisitedSetOnClick are better names IMO (assuming the property stays of course).

Edit: All names ideas so far:

  • SetVisitedOnClick
  • IsVisitedAutoSet
  • IsVisitedSetOnClick
  • AutoSetVisited

robloo avatar Nov 06 '22 15:11 robloo