メモの日々


2022年06月03日(金) [長年日記]

[windows] PowerShellで.Netのオブジェクトのインスタンス化

PowerShellでオブジェクトのインスタンスを作る方法は色々あるみたい。

通常はこのページのStatic new() methodにある new() を呼び出す方法が分かりやすいのだと思う。

PS C:\> $a = [Collections.Generic.List[string]]::new()
PS C:\> $a.Add("hello")
PS C:\> $a.Add("world")
PS C:\> $a
hello
world
PS C:\>

なお、以前

PowerShellでは、Add-Typeを使うとDLLを読み込んで.NETのクラスを使えるようになる。

とメモしたが、いくつかのDLLはプリロードされていてAdd-Typeを使わずとも使用できるようで、Syhstem.Collections.Generic.List<T>が含まれる mscorlib.dll はプリロード対象みたい。

また、名前空間の System は省略できるようなので、上記のように書ける。


2022年04月04日(月) [長年日記]

[windows] PowerShellで角括弧を含むファイルにアクセスする

PowerShell上で例えば、

  • test[1].txt

というファイルの内容を表示しようと

> get-content test[1].txt

としてもエラーになってしまう。

get-content : 指定されたパス test[1].txt にオブジェクトが存在しないか、-Include または -Exclude パラメーターによってフィルターされています。
発生場所 行:1 文字:1
+ get-content test[1].txt
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
    + FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand

原因は、角括弧がPowerShellのワイルドカードと解釈されるからみたい。

解決するには、「`」を使ってワイルドカード文字をエスケープする(更にシングルクウォートで囲う必要がある)

> get-content 'test`[1`].txt'

か、 -LiteralPath オプションを使用する。

> get-content -literalpath test[1].txt

2022年04月01日(金) [長年日記]

[windows][howto] PowerShellでJSONを手軽に整形

Windowsで1行のJSONを整形して表示したかった。

フォーマットにこだわらなければ、PowerShell上でConvertFrom-JsonConvertTo-Jsonを組合せて次のようにするのが一番手軽に思えたがどうだろうか。

convertfrom-json '{"a": 1, "b1": {"b2": {"b3": {"b4": 2}}}, "c": ["hello", "world"]}' `
| convertto-json
{
    "a":  1,
    "b1":  {
               "b2":  {
                          "b3":  "@{b4=2}"
                      }
           },
    "c":  [
              "hello",
              "world"
          ]
}

ただし、よく見ると "b3" の所がおかしい。

これは ConvertTo-Json に -Depth オプションを指定すれば解決する。

convertfrom-json '{"a": 1, "b1": {"b2": {"b3": {"b4": 2}}}, "c": ["hello", "world"]}' `
| convertto-json -depth 3
{
    "a":  1,
    "b1":  {
               "b2":  {
                          "b3":  {
                                     "b4":  2
                                 }
                      }
           },
    "c":  [
              "hello",
              "world"
          ]
}

2022年03月18日(金) [長年日記]

[windows] Excelで複数のセルの数値を一括変更する

最近は仕事でExcelばかり使っている。

Excelで複数のセルの数値を一括で変更するにはどうするか。例えば、

画像の説明

画像の説明

に変更したい。

形式を選択肢て貼り付けを使う

「形式を選択肢て貼り付け」の機能を使うと実現できた。次のようにする。

  1. 「900」を入力したセルを用意する。
  2. 「900」のセルを「コピー」する。
  3. 値を変更したいセルをすべて選択して、右クリックメニューから「形式を選択して貼り付け」を選ぶ。
  4. 表示された「形式を選択して貼り付け」ダイアログにてラジオボタンを次のように選択しOKをクリックする。
    • 「貼り付け」を「値」
    • 「演算」を「加算」

画像の説明

手軽でよい。


2022年01月28日(金) [長年日記]

[c#] WPFのListViewに対するクリック処理を添付ビヘイビアで実装

WPFのListViewに対し、その項目をクリックしたときに何かをする処理はどう書くのか。

にListViewItemへイベントハンドラを設定する例が書かれている。これだとViewのコードビハインドに処理を書くことになるが、コードビハインドを使いたくない場合もあるだろう。

ビヘイビア

コードビハインドを使わずにイベント処理などを行う方法として添付プロパティの仕組みを利用した「添付ビヘイビア」という手法がある。

また、添付ビヘイビアをライブラリ化して使いやすくしたものがBlendでSystem.Windows.Interactivityという名前空間で提供されていたが、現在ではそれがXamlBehaviors for WPFとしてMicrosoft.Xaml.Behaviors.Wpfという名前空間で提供されているようだ。このライブラリはNuGetから導入することができる。ドキュメントもあるがすべてが説明されているわけではなさそう。

ListViewのクリック処理を添付ビヘイビアで実装

XamlBehaviors for WPFを使うと少し短く書けるが、添付ビヘイビアの形で実装してもそれほど違いはなさそうなのでここでは添付ビヘイビアを使う例をメモする。

作るのは次のウィンドウだ。左側にあるListViewの項目をクリックすると、クリックした項目の名前が右側に表示される

ListViewを使った画面

XAMLは次のように書く。

<Window x:Class="WpfStudy.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:WpfStudy"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="400">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <ListView Grid.Column="0" ItemsSource="{Binding Items}"
                  local:ListViewMouseBehavior.LeftDownCommand="{Binding SetMessageCommand}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="番号" DisplayMemberBinding="{Binding Number}"/>
                    <GridViewColumn Header="名前" DisplayMemberBinding="{Binding Name}"/>
                </GridView>
            </ListView.View>
        </ListView>

        <TextBlock Grid.Column="1" Name="textBlock1" Text="{Binding Message}" FontSize="48"/>
    </Grid>
</Window>
  • ListViewに「local:ListViewMouseBehavior.LeftDownCommand="{Binding SetMessageCommand}"」と添付プロパティを設定している。これがListViewがクリックされたときにコマンドを呼び出す添付ビヘイビアだ。

添付ビヘイビアは次のようになる。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfStudy
{
    public static class ListViewMouseBehavior
    {
        // 本クラスが公開する添付プロパティ。
        // マウス左クリック時に実行するコマンドを保持する。
        // コマンドのパラメータにはクリックされたListViewItemのContextを設定している。
        public static readonly DependencyProperty LeftDownCommandProperty =
        DependencyProperty.RegisterAttached(
            "LeftDownCommand",
            typeof(ICommand),
            typeof(ListViewMouseBehavior),
            new PropertyMetadata(OnLeftDownCommandPropertyChanged));

        public static ICommand GetLeftDownCommand(ListView target) =>
            (ICommand)target.GetValue(LeftDownCommandProperty);

        public static void SetLeftDownCommand(ListView target, ICommand value) =>
            target.SetValue(LeftDownCommandProperty, value);

        // LeftDownCommandPropertyに対するPropertyChangedCallback。
        // 対象のListViewに対しマウス左クリック時のイベントハンドラを設定する。
        private static void OnLeftDownCommandPropertyChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            if (d is not UIElement ue) return;

            if (e.OldValue != null) ue.PreviewMouseLeftButtonDown -= ExecuteCommand;
            if (e.NewValue != null) ue.PreviewMouseLeftButtonDown += ExecuteCommand;
        }

        // ListViewに対するマウス左クリック時のイベントハンドラ。
        // プロパティに設定されているコマンドを実行する。
        private static void ExecuteCommand(object sender, MouseButtonEventArgs e)
        {
            if (sender is not ListView listView) return;
            if (e.OriginalSource is not DependencyObject source) return;

            var item = FindAncestor<ListViewItem>(source);
            if (item == null) return;

            var command = e.ChangedButton switch
            {
                MouseButton.Left => GetLeftDownCommand(listView),
                _ => null
            };
            if (command == null) return;

            var param = item.Content;
            if (command.CanExecute(param))
            {
                command.Execute(param);
                e.Handled = true;
            }
        }

        // Visual Treeをoから根の方に辿り、見つかったT型のオブジェクトを返す。
        private static T? FindAncestor<T>(DependencyObject o) where T : DependencyObject
        {
            if (o is T result) return result;

            var parent = VisualTreeHelper.GetParent(o);
            return parent == null ? null : FindAncestor<T>(parent);
        }
    }
}
  • 以前書いたようにして添付プロパティを実装している。
  • 添付プロパティのメタデータに指定するPropertyChangedCallbackにて、ListViewのPreviewMouseLeftButtonDownイベントにハンドラを設定している。このようにしてコードビハインド以外でイベント処理を実装する手法が添付ビヘイビアと呼ばれていると理解している。
  • MouseLeftButtonDownではなくPreviewMouseLeftButtonDownイベントを使っているのは、MouseLeftButtonDownのハンドラが呼ばれなかったから。そういうこともあるとリファレンスのImportantの所に書かれている。
  • クリックされた項目のデータを得るにはイベントハンドラにてListViewItemオブジェクトを取得する必要がある。MouseButtonEventArgsオブジェクトのOriginalSourceからビジュアルツリーを根の方に辿ってListViewItemオブジェクトを見つけるということをしている。

上のXAMLで定義したウィンドウのDataContextには次のクラスのインスタンスをセットした。Windows Community Toolkitに含まれるMVVM Toolkitを使っている。

using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Input;
using System.Collections.Generic;
using System.Windows.Input;

namespace WpfStudy
{
    class MainWindowModel : ObservableObject
    {
        public MainWindowModel()
        {
            Items = new List<Item>
            {
                new Item(1, "ねずみ"),
                new Item(2, "うし"),
                new Item(12, "いのしし"),
            };

            SetMessageCommand = new RelayCommand<Item>(x => Message = x?.Name ?? "");
        }

        public IEnumerable<Item> Items { get; }

        public string Message
        {
            get => _message;
            set => SetProperty(ref _message, value);
        }
        private string _message = "";

        public ICommand SetMessageCommand { get; }
    }
}
using System.Windows;

namespace WpfStudy
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            var w = new MainWindow
            {
                DataContext = new MainWindowModel(),
            };
            w.Show();
        }
    }
}