Reputation: 61401
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?
Upvotes: 8
Views: 10345
Reputation: 2363
The 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.
Upvotes: 7
Reputation: 2234
Old thread, but for those still coming across it like me, Hyperlink
does support ICommandBinding
, but you need a derived class to do so:
using System.Windows.Documents;
public class HyperlinkCommander : Hyperlink
{
protected override void OnClick()
{
Command.Execute(null);
}
}
Then in your xaml use it just like you would when binding a Button
to your ICommand in your ViewModel, in this case called HyperlinkClickerCommand. Something like this:
<localControls:HyperlinkCommander Command="{Binding HyperlinkClickerCommand}" >
Click me!
</localControls:HyperlinkCommander>
With something like this near the top of your xaml:
<Window
...
xmlns:localControls="clr-namespace:TestProject.Controls"
...
>
Where HyperlinkClickerCommand
is your command in your ViewModel that you are binding to. Your hyperlink as above will trigger the Execute of the bound command, and honour the CanExecute of it.
Upvotes: 0
Reputation: 80272
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.
}
}
Upvotes: 10