【WPF】アプリケーションランチャー

コンピュータ

リストボックスにアイコンの画像を表示するとアプリケーションランチャーのような見た目が作れると思い試作してみました。

機能

  1. アプリケーションの登録…ウィンドウに実行ファイル(.exe)をドラッグアンドドロップしてください。
  2. アプリケーションの起動…アイコンをダブルクリックしてください。
  3. アプリケーションの削除…アプリケーションが選択された状態でDeleteキーを押してください。

プロジェクトの作成


mkdir AppLanch
cd AppLanch
dotnet new wpf
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF
dotnet add package System.Drawing.Common
code.

ソースコード

<Window x:Class="AppLanch.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AppLanch"
        xmlns:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
        xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Window.InputBindings>
        <KeyBinding
            Gesture="Delete"
            Command="{Binding DeleteCommand}"/>
    </Window.InputBindings>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Closed">
            <interactivity:EventToReactiveCommand Command="{Binding WindowClosedCommand}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid AllowDrop="True">
        <ListBox
                x:Name="ListBox1"
                SelectedIndex="{Binding AppListIndex.Value}"
                ItemsSource="{Binding AppList}"
                >
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseDoubleClick">
                        <interactivity:EventToReactiveCommand Command="{Binding MouseDoubleClickCommand}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>            
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding Ico}"
                                   Width="50"
                                   Height="50"
                                   Margin="10"
                                   />                        
                            <TextBlock Text="{Binding Name}"
                                       Margin="10"
                                       />
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
        </ListBox>
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="DragOver">
                <interactivity:EventToReactiveCommand Command="{Binding DragOverCommand}" />
            </i:EventTrigger>
            <i:EventTrigger EventName="Drop">
                <interactivity:EventToReactiveCommand Command="{Binding DropCommand}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Grid>
</Window>

using System.Diagnostics;
using System;
using System.ComponentModel;
using Reactive.Bindings;
using System.Reactive.Disposables;

using System.Windows.Media.Imaging;


using Reactive.Bindings.Extensions;
using System.Windows;
using System.IO;

using System.Text.Json;
using System.Text.Json.Serialization;

namespace AppLanch
{
    public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
    {
        public static string IniPath = @".\AppLanch.json";
        public event PropertyChangedEventHandler? PropertyChanged;
        private CompositeDisposable Disposable { get; } = new ();
        public ReactiveCommand<EventArgs> WindowClosedCommand { get; }
        protected virtual void OnPropertyChanged(string name)
        {
            if (PropertyChanged == null) return;
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }


        public class AppInfo
        {
            public string? Name {get; set;}
            public string? FullName {get; set;}
            public BitmapImage? Ico {get; set;}
            public AppInfo(string name, string fullName)
            {
                Name = name;
                FullName = fullName;
            }
        }

        public ReactiveProperty<int> AppListIndex { get; set; } = new ReactiveProperty<int>(-1);
        public ReactiveCollection<AppInfo> AppList { get; set; } = new ReactiveCollection<AppInfo>();
        public ReactiveCommand<DragEventArgs>? DragOverCommand { get; }
        public ReactiveCommand<DragEventArgs>? DropCommand { get; }
        public ReactiveCommand<EventArgs> MouseDoubleClickCommand { get; }
        public ReactiveCommand? DeleteCommand { get; }
        BitmapImage? GetIcon(string filePath)
        {
            var ico = System.Drawing.Icon.ExtractAssociatedIcon(filePath);
            if (ico == null) return null;
            using (var ms = new System.IO.MemoryStream())
            {
                var bitmap = ico.ToBitmap();
                bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
                ms.Position = 0;
                var bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.CreateOptions = BitmapCreateOptions.None;
                bitmapImage.StreamSource = ms;
                bitmapImage.EndInit();
                bitmapImage.Freeze();
                return bitmapImage;
            }
        }
        public MainWindowViewModel()
        {
            PropertyChanged += (o, e) => {};

            // アプリ情報の読込
            if (File.Exists(IniPath))
            {
                using (var fs = File.OpenRead(IniPath))
                {
                    using (var stream = new StreamReader(fs, System.Text.Encoding.UTF8))
                    {
                        while (!stream.EndOfStream)
                        {
                            var s = stream.ReadLine();
                            if (s != null)
                            {
                                var app = JsonSerializer.Deserialize<AppInfo>(s);
                                if (app != null)
                                {
                                    if (app.FullName != null)
                                    {
                                        app.Ico = GetIcon(app.FullName);
                                    }
                                    AppList.AddOnScheduler(app);
                                }
                            }
                        }
                    }
                }               
            }
                        
            WindowClosedCommand = new ReactiveCommand<EventArgs>()
                .WithSubscribe(e =>this.Dispose());

            DragOverCommand = new ReactiveCommand<DragEventArgs>()
                .WithSubscribe(e=>
                {
                    if (e.Data.GetDataPresent(DataFormats.FileDrop))
                    {
                        e.Effects = DragDropEffects.Copy;
                        e.Handled = true;
                    }
                    else
                    {
                        e.Effects = DragDropEffects.None;
                        e.Handled = false;
                    }
                }).AddTo(Disposable);
            // アプリの登録            
            DropCommand = new ReactiveCommand<DragEventArgs>()
                .WithSubscribe(e=>
                {
                    var files = (string[])e.Data.GetData(DataFormats.FileDrop);
                    var f = files[0];
                    if (Path.GetExtension(f).ToUpper() != ".EXE") return;

                    var name = System.IO.Path.GetFileNameWithoutExtension(f);
                    var app = new AppInfo(name, f);
                    if (app.FullName != null)
                    {
                        app.Ico = GetIcon(app.FullName);
                    }
                    AppList.AddOnScheduler(app);                
                }).AddTo(Disposable);
            // アプリの起動
            MouseDoubleClickCommand = new ReactiveCommand<EventArgs>()
                .WithSubscribe(e =>
                {
                    if (AppListIndex.Value < 0) return;
                    var path = AppList[AppListIndex.Value].FullName;
                    if (path == null) return;

                    Debug.WriteLine(path);
                    Process.Start(path);
                }).AddTo(Disposable);
            // 要素の削除
            DeleteCommand = new ReactiveCommand()
                .WithSubscribe(()=>{
                    if (AppList.Count == 0) return;
                    if (AppListIndex.Value < 0) return;
                    AppList.RemoveAtOnScheduler(AppListIndex.Value);
                    
                }).AddTo(Disposable);
        }
        // 終了処理
        public void Dispose()
        {
            // アプリ情報の保存
            var options = new JsonSerializerOptions() {
                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.
                Create(System.Text.Unicode.UnicodeRanges.All)};
            using (var fs = File.Create(IniPath))
            {
                using (var stream = new StreamWriter(fs, System.Text.Encoding.UTF8))
                {
                    foreach (var app in AppList)
                    {
                        app.Ico = null; 
                        string str = JsonSerializer.Serialize<AppInfo>(app, options);
                        stream.WriteLine(str);
                    }
                }
            }            
            Debug.WriteLine("Dispose");
            Disposable.Dispose();
        }
    }
}

コメント