Search code examples
c#wpfmvvmviewmodellauncher

Creating a launcher in WPF


I'm currently in the process of writing a launcher and I'm in the prototyping phase of it.

I'm really new to WPF but not to the MVVM architecture, however, I'm not sure how to bind my ViewModel to the CommandBox view because I'm using methods in my ViewModel, maybe I'm looking at it from the wrong angle so if I'm doing something wrong please enlighten me! :)

I have the following ViewModel that basically is really the essence of the launcher in terms of its input:

/// <summary>
/// Provides a buffer of characters in order to search for candidates that matches the buffer.
/// </summary>
public class CommandBufferViewModel : INotifyPropertyChanged
{
    private readonly StringBuilder _input;

    public CommandBufferViewModel()
    {
        _input = new StringBuilder();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Deletes a character from the buffer.
    /// </summary>
    public void DeleteKey(int index, int length = 1)
    {
        _input.Remove(index, length);

        OnPropertyChanged(nameof(DeleteKey));
    }

    /// <summary>
    /// Adds a character to the buffer.
    /// </summary>
    public void ProcessKey(int index, char key)
    {
        _input.Insert(index, key);

        OnPropertyChanged(nameof(ProcessKey));
    }

    /// <summary>
    /// Returns results that matches the current buffer.
    /// </summary>
    /// <returns>Results that matches the current buffer.</returns>
    public async Task<CommandResults> Search()
    {
        // NYI

        return default(CommandResults);
    }

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The idea here is that commands can be stored in a remote database or whatever storage you may like as well as locally.

The (CommandBox view) TextBox looks like this:

<TextBox
    x:Class="Yalla.Launcher.Views.CommandBox"
    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:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:bs="clr-namespace:Yalla.Launcher.Behaviors"
    mc:Ignorable="d" 
    Width="Auto" Height="Auto" VerticalContentAlignment="Center" BorderThickness="1">
    <i:Interaction.Behaviors>
        <bs:WatermarkBehavior Text="Enter Command... (type ? for help)" />
    </i:Interaction.Behaviors>
</TextBox>

Solution

  • I think I figured this out, I'll simply have a ViewModel that holds the text that is the command the user is searching for and I'll use this as the glue between the view and the search service of the launcher.

    Something like this:

    namespace Yalla.Launcher.ViewModels
    {
        using System.ComponentModel;
    
        using Commands;
    
        using Features.Commands.Search;
    
        public class SearchViewModel : INotifyPropertyChanged, ISearchableText
        {
            private SearchCommandHandler _enterCommandHandler;
    
            private string _searchText;
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public SearchCommandHandler Search => _enterCommandHandler ?? (_enterCommandHandler = new SearchCommandHandler(new SearchService(this)));
    
            public string SearchText
            {
                get
                {
                    return _searchText;
                }
    
                set
                {
                    _searchText = value;
    
                    OnPropertyChanged(nameof(SearchText));
                }
            }
    
            protected virtual void OnPropertyChanged(string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

    Finally, I bind it to the TextBox like this:

    <ctrls:LauncherTextBox
        x:Class="Yalla.Launcher.Views.LauncherSearchBox"
        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:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:bs="clr-namespace:Yalla.Launcher.Behaviors"
        xmlns:ctrls="clr-namespace:Yalla.Launcher.Controls"
        xmlns:vm="clr-namespace:Yalla.Launcher.ViewModels"
        mc:Ignorable="d"
        Width="Auto" Height="Auto" 
        VerticalContentAlignment="Center" BorderThickness="1" 
        Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <ctrls:LauncherTextBox.DataContext>
            <vm:SearchViewModel />
        </ctrls:LauncherTextBox.DataContext>
        <i:Interaction.Behaviors>
            <bs:WatermarkBehavior Text="Enter Command... (type ? for help)" />
        </i:Interaction.Behaviors>
        <TextBox.InputBindings>
            <KeyBinding Key="Return" Command="{Binding Search}"></KeyBinding>
        </TextBox.InputBindings>
    </ctrls:LauncherTextBox>
    

    Quite simple but that's probably what I need to start with.