Question

I am trying to implement Help functionality for my wpf application which is following the MVVM pattern. I have my help file present, which contains many pages according to the application. Now I need to integrate this into my application.

Here are my requirements:

  1. Pressing F1 opens a certain page in the help file depending on the view model. For this, I guess, I need to bind the F1 command to my view model. How do we bind keys in views?
  2. Pressing F1 on a text field opens help for that text field. I think it will be the same as requirement 1. But the problem here is how will I know that a certain text field, button, or radio button is selected?
Was it helpful?

Solution

  1. Listen for the key in the view (or a base class of the view) and call execute on a HelpCommand on the DataContext.

  2. Pass the control that has focus (or its id, or tag, ...) as an argument to the HelpCommand.

Alternative way to find the focussed control by using the FocusManager

Here is an example:

ContextHelp C#:

public static class ContextHelp
{
    public static readonly DependencyProperty KeywordProperty =
        DependencyProperty.RegisterAttached(
            "Keyword",
            typeof(string),
            typeof(ContextHelp));

    public static void SetKeyword(UIElement target, string value)
    {
        target.SetValue(KeywordProperty, value);
    }

    public static string GetKeyword(UIElement target)
    {
        return (string)target.GetValue(KeywordProperty);
    }
}

ViewBase:

public abstract class ViewBase : UserControl
{
    public ViewBase()
    {
        this.KeyUp += ViewBase_KeyUp;
        this.GotFocus += ViewBase_GotFocus;
    }

    void ViewBase_GotFocus(object sender, RoutedEventArgs e)
    {
        FocusManager.SetIsFocusScope(this, true);
    }

    void ViewBase_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.F1)
        {
            var viewModel = this.DataContext as ViewModelBase;
            if (viewModel != null)
            {
                var helpTopic = "Index";
                var focusedElement = 
                    FocusManager.GetFocusedElement(this) as FrameworkElement;
                if (focusedElement != null)
                {
                    var keyword = ContextHelp.GetKeyword(focusedElement);
                    if (!String.IsNullOrWhiteSpace(keyword))
                    {
                        helpTopic = keyword;
                    }
                }
                viewModel.HelpCommand.Execute(helpTopic);
            }
        }
    }
}

ViewModelBase:

public abstract class ViewModelBase: INotifyPropertyChanged
{
    public ICommand HelpCommand { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName="")
    {
        var p = PropertyChanged;
        if (p != null)
        {
            p(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

AViewModel:

class AViewModel : ViewModelBase
{
    public AViewModel()
    {
        HelpCommand = new RelayCommand(HelpCommandExecuted, (p)=>true);
    }

    private void HelpCommandExecuted(object parameter)
    {
        var topic = parameter as string;
        if (!String.IsNullOrWhiteSpace(topic))
        {
            HelpText = String.Format("Information on the interesting topic: {0}.", topic);
        }
    }

    private string _helpText;

    public string HelpText
    {
        get { return _helpText; }
        private set
        {
            if (_helpText != value)
            {
                _helpText = value;
                OnPropertyChanged();
            }
        }
    }
}

AView C#:

public partial class AView : ViewBase
{
    public AView()
    {
        InitializeComponent();
    }
}

AView XAML:

<local:ViewBase x:Class="WpfApplication2.AView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApplication2"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Label Content="{Binding HelpText}" Margin="10,254,10,0" VerticalAlignment="Top" Height="36"/>
        <Button local:ContextHelp.Keyword="Button Info" Content="Button" HorizontalAlignment="Left" Margin="192,32,0,0" VerticalAlignment="Top" Width="75"/>
        <TextBox local:ContextHelp.Keyword="TextBox Info" HorizontalAlignment="Left" Height="23" Margin="29,32,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
        <CheckBox local:ContextHelp.Keyword="CheckBox Info" Content="CheckBox" HorizontalAlignment="Left" Margin="29,80,0,0" VerticalAlignment="Top"/>
        <ComboBox local:ContextHelp.Keyword="ComboBox Info" HorizontalAlignment="Left" Margin="138,80,0,0" VerticalAlignment="Top" Width="120"/>
    </Grid>
</local:ViewBase>

MainWindow XAML:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication2" x:Class="WpfApplication2.MainWindow"
        Title="MainWindow" Height="700" Width="500">
    <Grid x:Name="ViewPlaceholder">
    </Grid>
</Window>

MainWindow C#:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var view = new AView();
        var viewModel = new AViewModel();
        view.DataContext = viewModel;
        ViewPlaceholder.Children.Clear();
        ViewPlaceholder.Children.Add(view);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top