Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF MVVM bind Hyperlink RequestNavigate to View model

On WPF form I have a hyperlink that when clicked is supposed aggregate some data in database before redirecting to internal web page.

Currently XAML looks following:

<Hyperlink RequestNavigate="Hyperlink_RequestNavigate" IsEnabled="{Binding CanTakePayment}">
  Launch Payments Portal
</Hyperlink>

to do the db stuff Hyperlink_RequestNavigate method is used, that resides in View.xaml.cs

It looks something like:

private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
    var transactionReference = GetToken(100M, "13215", "product");
    var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, transactionReference);
    e.Handled = true;
}

I don't like this mechanism being here and would prefer to move it to View model.

What I tried to do is add to ViewModel property

public ICommand NavigateToTakePayment       
{
    get { return _navigateToTakePayment; }
    set { _navigateToTakePayment = value; }
}

and in XAML change binding to

<Hyperlink RequestNavigate="{Binding Path=NavigateToTakePayment}" IsEnabled="{Binding CanTakePayment}"> 
   Launch Payments Portal
</Hyperlink>

but it started giving me cast exceptions.

What is most appropriate way to move this mechanism from View to ViewModel?

like image 635
Matas Vaitkevicius Avatar asked Mar 08 '16 10:03

Matas Vaitkevicius


2 Answers

HyperLink is a bit of a problem child. It does not support command binding.

It's possible to shoehorn support for command binding into it with an attached property, but it's easier to just modify a button to do the same thing.

<Style TargetType="Button" x:Key="HyperlinkStyledButton">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="Button">
        <TextBlock Foreground="DodgerBlue"
                   Text="{TemplateBinding Content}"
                   TextDecorations="Underline" 
                   Cursor="Hand" />
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Then use the hyperlink like this:

<Button Command="{Binding OpenHttpLinkCommand}" Content="www.google.com" 
        Style="{StaticResource HyperlinkStyledButton}" ToolTip="Some custom tooltip"/>

Assuming that standard MVVM binding is working correctly:

In the ViewModel:

public ICommand OpenHttpLinkCommand { get; }

In the ViewModel constructor:

this.OpenHttpLinkCommand = new DelegateCommand(this.OnOpenHttpLinkCommand);

And the command to open the browser with a link:

private void OnOpenHttpLinkCommand()
{
    try
    {
        System.Diagnostics.Process.Start("http://www.google.com/");
    }
    catch (Exception)
    {
        // TODO: Error.
    }
}
like image 166
Contango Avatar answered Sep 28 '22 06:09

Contango


Problem with your app is that the ICommand is not initialized before use.
I have a Command implementation like so:

public class RelayCommand : ICommand
    {
        Predicate<object> _canExecute;
        Action<object> _execute;
        bool _defaultBehaviourForCanExecute;

        public RelayCommand(Action<object> execute, bool defaultBehaviourForCanExecute = true, Predicate<object> canExecute = null)
        {
            _canExecute = canExecute;
            _execute = execute;
            _defaultBehaviourForCanExecute = defaultBehaviourForCanExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute != null)
            {
                Logger.LogInformation("Evaluating can execute method for " + _canExecute.Method.DeclaringType + "->"+_canExecute.Method.Name);
                return _canExecute.Invoke(parameter);
            }
            return _defaultBehaviourForCanExecute;
        }

        public event EventHandler CanExecuteChanged;

        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, new EventArgs());
        }

        public void Execute(object parameter)
        {
            Logger.LogInformation("Executing command method for " + _execute.Method.DeclaringType + "->" + _execute.Method.Name);
            _execute.Invoke(parameter);
            RaiseCanExecuteChanged();
        }
    }  

Now this is being initialized in my ViewModel like so:

NavigateToTakePayment = new RelayCommand(navigateToTakePayment CommandMethod);//it also can take canExecute method if you need a condition before executing.  

then in your xaml you use it like this:

<Hyperlink RequestNavigate="{Binding Path=NavigateToTakePayment}" IsEnabled="{Binding CanTakePayment}">
    Launch Payments Portal
</Hyperlink>

BTW: when you Hyperlink needs to be disabled implement a canexecute method
and then it will be done automatically. If you need more info I will update my answer.
Happy coding

like image 40
XAMlMAX Avatar answered Sep 28 '22 04:09

XAMlMAX