Posted at

USBデバイスの抜き差しを検知する


やりたいこと

USBデバイスが抜き差しされたタイミングで、ViewModelのコマンドを実行する。


実装

USB抜き差し時にコマンドを実行するビヘイビアを作成する。


ビヘイビア

using System;

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;

namespace Sample {
public class DeviceChangeBehavior {
private static readonly uint WM_DEVICECHANGE = 0x0219;

public static DependencyProperty CommandProperty
= DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(DeviceChangeBehavior),
new PropertyMetadata(Command_PropertyChanged)
);

public static void SetCommand(FrameworkElement element, ICommand value)
=> element.SetValue(CommandProperty, value);

public static ICommand GetCommand(FrameworkElement element)
=> (ICommand)element.GetValue(CommandProperty);

private static Dictionary<FrameworkElement, ICommand> _commands
= new Dictionary<FrameworkElement, ICommand>();

private static void Command_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var element = d as FrameworkElement;
if (element == null) return;

if (e.NewValue != null) {
element.Loaded += FrameworkElement_Loaded;
_commands.Add(control, e.NewValue as ICommand);
} else {
element.Loaded -= FrameworkElement_Loaded;
var handle = ((HwndSource)HwndSource.FromVisual(element)).Handle;
var source = HwndSource.FromHwnd(handle);
source.RemoveHook(WndProc);
_commands.Remove(element);
}
}

private static void FrameworkElement_Loaded(object sender, RoutedEventArgs e) {
var element = sender as FrameworkElement;
if (element == null) return;

//ロード完了後でないとハンドラを登録できない
var handle = ((HwndSource)HwndSource.FromVisual(element)).Handle;
var source = HwndSource.FromHwnd(handle);

source.AddHook(WndProc);
}

private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
if (msg != WM_DEVICECHANGE)
return IntPtr.Zero;

_commands
.Select(x => new { ((HwndSource)HwndSource.FromVisual(x.Key))?.Handle, Command = x.Value })
.Where(x => x.Handle == hwnd && x.Command.CanExecute(null))
.ForEach(x => x.Command.Execute(null));

return IntPtr.Zero;
}
}
}



使い方


XAML

<Window x:Class="Sample.MainView"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Sample"
local:DeviceChangeBehavior.Command="{Binding XXXCommand}"
Title="MainView" Height="300" Width="300">
<Grid>
<TextBox />
</Grid>
</Window>

Window以外にも指定できるが、ルート要素に指定するのが無難。