20191201のC#に関する記事は6件です。

初心者がVisual Studio で始める Azure Kinect

3D Sensor Advent Calendar 2019の2日目の記事です。

概要

3D センサー歴 3 か月程度の初心者ですが、AzureKinect 手元にあるので、頑張ってみようかと。
目標としては、初心者レベルで Azure Kinect Sensor SDK 使って、接続、動作するところまで試してみたいと思います。

環境

  • windows 10 pro
  • Microsoft Visual Studio Community 2019 Version 16.3.9
  • Microsoft .NET Framework Version 4.8.03752
  • (Azure Kinect Sensor SDK 1.3.0)

環境としては、上記は Visual Studio しかインストールされていない状態で開始します。
NET デスクトップ開発
C++ によるデスクトップ開発
ユニバーサル Windows プラットフォーム開発
上記は適宜チェックをつけてインストールをしておいたほうが良いと思います。(最低限のモジュールまでは調べてないです)

まずは C++ で AzureKinect に接続

  • C++のコンソールアプリで新しいプロジェクトの作成からC++のコンソールアプリを指定

コメント 2019-11-30 194802.png

  • プロジェクト名と配置場所を指定し、空のアプリケーションを作成。

コメント 2019-11-30 195056.png

  • 次の画面で AzureKinectのキーワードで探しパッケージをインストールする。

コメント 2019-11-30 122106.png

  • プロジェクトが作成出来たら、 Azure Kinect の SDK を入れる。入れ方は以下
  1. NuGet で SDK をインストール
  2. AzureKinect Sendor SDK をPCにインストールし、モジュールのパスを通す の二つがあるが、今回は、1のNuGetで取得。「ツール」からNugetパッケージマネージャーの管理でGUIで入れる

コメント 2019-11-30 121841.png

  • 画面が表示される。このHello,World の部分にスクリプトを入れる。

コメント 2019-11-30 195134.png

ConsoleApplication1.cpp
#include <iostream>
#include <k4a/k4a.h>

int main()
{
    std::cout << "Hello World!\n";

    k4a_device_t device = NULL;

    if (K4A_RESULT_SUCCEEDED != k4a_device_open(K4A_DEVICE_DEFAULT, &device))
    {
        "Device open error!\n";
        return 2;
    }

    std::cout << "Opened device";

    k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
    config.camera_fps = K4A_FRAMES_PER_SECOND_30;
    config.color_format = K4A_IMAGE_FORMAT_COLOR_MJPG;
    config.color_resolution = K4A_COLOR_RESOLUTION_2160P;
    config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED;

    if (K4A_RESULT_SUCCEEDED != k4a_device_start_cameras(device, &config))
    {
        std::cout << "Failed to start!\n";
        return 3;
    }
    else
    {
        std::cout << "Camera started.\n";
    }

    k4a_device_stop_cameras(device);

    std::cout << "Device stopped.\n";

    k4a_device_close(device);

    std::cout << "Camera closed.\n";

}

  • ビルドからソリューションのビルドを実行。

コメント 2019-11-30 200746.png

  • アプリケーションを実行。ディレクトリや実行ファイル名などは適当に読み替えてください。
  • 実行すると以下のような結果となり、無事接続成功。
D:\>"D:\ConsoleApplication1\Debug\ConsoleApplication1.exe"
Hello World!
Opened deviceCamera started.
Device stopped.
Camera closed.

上記で、Visual Studio の C++ で Azure Kinect に接続することができました・・・

って何も動いてないよね

・・・すみません。C++バージョン、コンソールで接続、以外何も見えるものできてません。 C++ の描画ライブラリに手こずって、C++ で動かすところまでいきませんでした。なので、もう一つやります。

改めて C# サンプルを使って AzureKinect で接続、画面に表示できるところまで・・・

コメント 2019-12-01 191649.png

  • Download したら適当なフォルダに展開し、build2019/csharp 配下のサンプルを試す。

コメント 2019-12-01 191910.png

  • とりあえず 1-AquiringImages 下のソリューションファイル(.slnという拡張子)を開く。自分の環境ではセキュリティ警告とターゲットフレームワークがインストールされてませんと出るけどそのままOKを押す。

コメント 2019-12-01 192513.png

  • 忘れないように Azure Kinect Sensor SDK を NuGet でインストールする。
  • リリースにして、ソリューションのビルドを実行する。
  • ちなみに本体プログラムは Mainwindow.xaml.cs なので、詳細を変えたければここを変える。

コメント 2019-12-01 192759.png

  • 正常終了したら bin の中にプロジェクトの名前の実行可能なファイルが作成される。

コメント 2019-12-01 193152.png

  • 上からカラーカメラ取得 、 深度を色で表現したもの 、 色での識別、(4つめのはAzure の Cognitiveサービス使用が必要そうなので今回は割愛)

コメント 2019-12-01 193403.png
コメント 2019-12-01 195217.png
コメント 2019-12-01 195512.png

やっと、何とか表示までできました・・・。

振り返り

  • 今回は、 C++ と C# を初心者でも動かせるように解説したかった。すごい初歩だが、苦戦しながらなんとか完了。
  • でも、 世にある AzureKinect の C++ サンプルは、動作するまでかなり大変そう。(C++知識がないのと、ライブラリが各々違ってコンパイルがなかなかできない)
  • C# の方が描画などは楽かもしれない(でも AzureKinect の C#Sample も少ない)

ということで、次は Unity で頑張ってみます。

明日12/3(火)は @SatoshiGachiFujimoto さんの「PythonでRealSenseD400シリーズ入門」です。Python入門、私も書こうと思ってましたが、他のものにしようかな・・・楽しみにしています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WPF で画面端に張り付かせるビヘイビアを書いた。

当日に慌ててアドベントカレンダー記事を書くという体たらく

前提

必要なデータ

  • SystemParameters.VirtualScreenWidth
    画面全体の大きさ
  • SystemParameters.VirtualScreenHeight
    画面全体の高さ
  • Window の現在地 (Left, Top)

何がしたかったのか

Left, Top の値をバインディングしながら、ウィンドウ座標を変えたかった。

しかし、直接 Left Top に値をバインディングすると、ウィンドウ読み込み後にバインディングが切れる。

解決した方法

XAML で簡単にバインディングできるようにしたかったので、ビヘイビアにした。
書いたコードは以下

public class WindowLocationBehavior : Behavior<Window>
{
    public double Left
    {
        get => (double)this.GetValue(LeftProperty);
        set => this.SetValue(LeftProperty, value);
    }

    public static readonly DependencyProperty LeftProperty =
        DependencyProperty.Register("Left", typeof(double), typeof(WindowLocationBehavior), new UIPropertyMetadata((double)0.0));

    public double Top
    {
        get => (double)this.GetValue(TopProperty);
        set => this.SetValue(TopProperty, value);
    }

    public static readonly DependencyProperty TopProperty =
        DependencyProperty.Register("Top", typeof(double), typeof(WindowLocationBehavior), new UIPropertyMetadata(
            (double)0.0));

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Top = this.Top;
        AssociatedObject.Left = this.Left;
        AssociatedObject.LocationChanged += (sender, e) =>
        {
            var windowRightEdge = SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth;
            if (windowRightEdge - ((Window)sender).Width < ((Window)sender).Left) ((Window)sender).Left = windowRightEdge - ((Window)sender).Width;
            else if (SystemParameters.VirtualScreenLeft > ((Window)sender).Left) ((Window)sender).Left = SystemParameters.VirtualScreenLeft;
            if (SystemParameters.VirtualScreenHeight - ((Window)sender).Height < ((Window)sender).Top) ((Window)sender).Top = SystemParameters.VirtualScreenHeight - ((Window)sender).Height;
            else if (0 > ((Window)sender).Top) ((Window)sender).Top = 0;
            this.Left = ((Window)sender).Left;
            this.Top = ((Window)sender).Top;
        };
    }
}

解説

CSharp 側

public double Left
{
    get => (double)this.GetValue(LeftProperty);
    set => this.SetValue(LeftProperty, value);
}

public static readonly DependencyProperty LeftProperty =
    DependencyProperty.Register("Left", typeof(double),
    typeof(WindowLocationBehavior),
    new UIPropertyMetadata((double)0.0));

public double Top
{
    get => (double)this.GetValue(TopProperty);
    set => this.SetValue(TopProperty, value);
}

public static readonly DependencyProperty TopProperty =
    DependencyProperty.Register("Top", typeof(double),
    typeof(WindowLocationBehavior),
    new UIPropertyMetadata((double)0.0));

ここらへんはよくある添付プロパティと同じ。
依存関係プロパティの宣言

protected override void OnAttached()
{
    base.OnAttached();

    AssociatedObject.Top = this.Top;
    AssociatedObject.Left = this.Left;
    var windowRightEdge = SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth;
    AssociatedObject.LocationChanged += (sender, e) =>
    {
        if (windowRightEdge - ((Window)sender).Width < ((Window)sender).Left)
            ((Window)sender).Left = windowRightEdge - ((Window)sender).Width;
        else if (SystemParameters.VirtualScreenLeft > ((Window)sender).Left)
            ((Window)sender).Left = SystemParameters.VirtualScreenLeft;
        if (SystemParameters.VirtualScreenHeight - ((Window)sender).Height < ((Window)sender).Top)
            ((Window)sender).Top = SystemParameters.VirtualScreenHeight - ((Window)sender).Height;
        else if (0 > ((Window)sender).Top)
            ((Window)sender).Top = 0;
        this.Left = ((Window)sender).Left;
        this.Top = ((Window)sender).Top;
    };
}

アタッチされた時に、アタッチされたウィンドウの座標をバンディングされてる値で書き換える。

ウィンドウの右端の座標を計算する。(左側のモニターの座標がマイナス値だったりするので重要。)

ウィンドウの座標変化時のイベントを購読して以下の処理をする。

  • 右端のx座標値からウィンドウ幅を引いた値 が ウィンドウの左端上座標より小さい場合
    右端に張り付かせる。
  • 左端のx座標値 がウィンドウの左端上x座標値よりも大きい場合
    左端に張り付かせる

XAML 側

こんな感じで書く

<Window x:Class="Test.Views.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:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:Test.Views"
        xmlns:be="clr-namespace:Test.Behaviors"
        mc:Ignorable="d"
        Title="Sample" SizeToContent="WidthAndHeight">
    <i:Interaction.Behaviors>
        <be:WindowLocationBehavior Left="{Binding WindowManager.WindowData.Left, Mode=TwoWay}"
            Top="{Binding WindowManager.WindowData.Top, Mode=TwoWay}"/>
    </i:Interaction.Behaviors>
</Window>

Interaction.Behaviors に登録、Left と Top の値にそれぞれバインディングする値を書いて終わり。

まとめ

ビヘイビア自作をすれば、バインディング値を使いながらイベント購読して値を書き換えたりできる。
Microsoft.Xaml.Behaviors はいいぞ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# - VSCodeスニペットを読み込むツールを作ってみた

C# よく使うusing 名前空間 / (Windows)Form テンプレ作った (Visual Studio使わない人向け)
の続き。

残存課題:

  • Jsonは順序性を気にしないものらしく、入力データと読み込んだときのデータの並びが変わってしまう。
  • メニューとかctrl+Sでの保存は未対応

"body" の中身の整形する機能だけのツールを作ったほうが使いやすい気がしてきた・・

環境

Windows10

仕様概要

VisualStudioCodeのスニペットのJsonは、標準のJsonではないようで1、全体を読み込むと例外を吐いてしまうため、一部のみを編集対象とすることにした。

Json内の
// %%kob58im-ToolBegin%% と
// %%kob58im-ToolEnd%%
に囲まれた部分をJsonとして扱い、読み込みます。(複数ペアは不可)

画面キャプチャ

image.png

やってた最中にはまったこと

・VSCodeのJsonは改行コードが\nのよう

・C#のRegexのRegexOptions.Multiline指定時の$は\r\nにはマッチしない(\nにマッチする)。
 参考: http://gushwell.ldblog.jp/archives/50221903.html

・Microsoft Docsにあるメンバの名前が間違っている?
 https://qiita.com/kob58im/items/a743bfcdf929c3ec6771

ソースコード

#define DEBUG

using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Windows.Data.Json;// to use JsonValue class


public class MyJsonItemInfo
{
    public string Name{get;set;}
    public string Description{get;set;}
    public string Prefix{
        get{return String.Join(", ", _prefix);}
        set{_prefix = value.Split(new Char[]{','}).Select(x=>x.Trim()).ToArray();}
    }
    public string Body{
        get{return String.Join("\r\n", _body);}
        set{_body = Regex.Split(value,@"\r?\n");}
    }
    public string BodyFirstLine{get{return (_body.Length==0)?"":_body[0];}}
    public string[] BodyAsArray{get{return _body;}}
    public int Lines{get{return _body.Length;}}

    string[] _prefix;
    string[] _body;


    public MyJsonItemInfo(string keyName, JsonObject jo)
    {
        Name = keyName;

        JsonValue jvPrefix;
        _prefix = MyJsonAsStringArray("prefix", jo, out jvPrefix) ?? new string[]{};

        JsonValue jvDescription;
        Description = MyJsonAsString("description", jo, out jvDescription) ?? "";

        JsonValue jvBody;
        _body = MyJsonAsStringArray("body", jo, out jvBody) ?? new string[]{};
    }


    static string[] MyJsonAsStringArray(string keyName, JsonObject joItem, out JsonValue jv)
    {
        try {
            jv = joItem.GetNamedValue(keyName);
        }
        catch ( Exception ) {
            jv = null;
            return new string[]{};
        }

        if ( jv.ValueType == JsonValueType.String ) {
            return new string[]{jv.GetString()};
        }
        else if ( jv.ValueType == JsonValueType.Array ) {
            return MyJsonArrayToStringArray(jv.GetArray());
        }
        else {
            return new string[]{};
        }
    }

    static string MyJsonAsString(string keyName, JsonObject joItem, out JsonValue jv)
    {
        try {
            jv = joItem.GetNamedValue(keyName);
            // 第2引数にデフォルト値として JsonValue.CreateNullValue() を入れようとしたが、なぜか無い..
        }
        catch ( Exception ) {
            jv = JsonValue.CreateStringValue("");
            return "";
        }

        if ( jv.ValueType == JsonValueType.String ) {
            return jv.GetString();
        }
        else {
            return "";
        }
    }

    static string[] MyJsonArrayToStringArray(JsonArray a)
    {
        var s = new string[a.Count];
        for ( int i=0;i<a.Count;i++ ) {
            s[i] = a.GetStringAt((uint)i);
        }
        return s;
    }

    public string MakeStringOfJson()
    {
        return JsonValue.CreateStringValue(Name).Stringify() + ":" + (MakeJsonObject().Stringify());
    }

    JsonObject MakeJsonObject()
    {
        var jo = new JsonObject();

        JsonValue jvDescription = JsonValue.CreateStringValue(Description);
        jo.Add("description", jvDescription);

        if (_prefix.Length==1){
            JsonValue jvPrefix = JsonValue.CreateStringValue(_prefix[0]);
            jo.Add("prefix", jvPrefix); // Insertがない
        }
        else {
            var jaPrefix = new JsonArray();
            foreach ( string s in _prefix ) {
                jaPrefix.Add(JsonValue.CreateStringValue(s)); // Appendがない
            }
            jo.Add("prefix", jaPrefix);
        }

        var jaBody = new JsonArray();
        foreach ( string s in _body ) {
            jaBody.Add(JsonValue.CreateStringValue(s));
        }
        jo.Add("body", jaBody);

        return jo;
    }
}


class VscodeSnippetEditor : Form
{
    static string GetDefaultSnippetPath()
    {
#if DEBUG
        return (new FileInfo("csharp.json.txt")).FullName;
#else
        return Path.Combine(Environment.GetEnvironmentVariable("appdata"),
                             @"code\user\snippets\csharp.json");
#endif
    }

    TextBox txtName;
    TextBox txtPrefix;
    TextBox txtBody;
    TextBox txtDescription;
    SplitContainer spl;
    ListView lsv;

    readonly string Title = "Snippet editor";

    bool NotSavedYet{
        get{return _notSaved;}
        set{
            _notSaved = value;
            if(_notSaved){Text=Title+"*";}else{Text=Title;}
        }
    }
    bool _notSaved;

    string _head;
    string _tail;
    bool _txtChangedByProgram;

    readonly int ColumnIndex_Name   = 0;
    readonly int ColumnIndex_Prefix = 1;
    readonly int ColumnIndex_Lines  = 2;
    readonly int ColumnIndex_Body   = 3;
    readonly int ColumnIndex_Description = 4;

    VscodeSnippetEditor()
    {
        ClientSize = new Size(800, 600);
        Text=Title;

        spl = new SplitContainer();
        spl.Dock = DockStyle.Fill;
        spl.Orientation = Orientation.Horizontal;//Vertical;
        spl.SplitterDistance = 400;
        Controls.Add(spl);

        lsv = new ListView();
        lsv.View = View.Details;
        lsv.FullRowSelect = true;
        lsv.HideSelection = false;
        lsv.MultiSelect = false;
        lsv.GridLines = true;
        // 入れ替えなどでindexが変わる場合は ColumnIndex_xxxx もメンテすること
        lsv.Columns.Add("Name",        100, HorizontalAlignment.Left);
        lsv.Columns.Add("prefix",      100, HorizontalAlignment.Left);
        lsv.Columns.Add("lines",        50, HorizontalAlignment.Left);
        lsv.Columns.Add("body",        250, HorizontalAlignment.Left);
        lsv.Columns.Add("description", 300, HorizontalAlignment.Left);
        lsv.Dock = DockStyle.Fill;
        lsv.SelectedIndexChanged += Lsv_SelectedIndexChanged;
        spl.Panel1.Controls.Add(lsv);


        txtName = new TextBox();
        //txtName.Text = @"name";
        txtName.Location = new Point(0,0);
        txtName.Width = 200;
        txtName.TextChanged += TxtName_TextChanged;
        spl.Panel2.Controls.Add(txtName);

        txtPrefix = new TextBox();
        //txtPrefix.Text = @"prefix";
        txtPrefix.Location = new Point(0,30);
        txtPrefix.Width = 200;
        txtPrefix.TextChanged += TxtPrefix_TextChanged;
        spl.Panel2.Controls.Add(txtPrefix);

        txtDescription = new TextBox();
        //txtDescription.Text = @"description";
        txtDescription.Location = new Point(0,60);
        txtDescription.Width = 400;
        txtDescription.TextChanged += TxtDescription_TextChanged;
        spl.Panel2.Controls.Add(txtDescription);

        txtBody = new TextBox();
        txtBody.Multiline = true;
        txtBody.ScrollBars = ScrollBars.Both;
        //txtBody.Text = @"body";
        txtBody.Location = new Point(0,90);
        txtBody.Size = new Size(500, 300);
        txtBody.Font = new Font("MS ゴシック", txtBody.Font.Size, txtBody.Font.Unit);
        txtBody.TextChanged += TxtBody_TextChanged;
        spl.Panel2.Controls.Add(txtBody);

        Load += (s,e)=>{
            MyResize();
            LoadSnippetToListView(GetDefaultSnippetPath());
            ClearText();
        };

        Resize    += (s,e)=>{MyResize();};
        ResizeEnd += (s,e)=>{MyResize();};
        spl.SplitterMoving += (s,e)=>{MyResize();};
        spl.SplitterMoved  += (s,e)=>{MyResize();};

        FormClosing += Form_FormClosing;
    }

    void MyResize()
    {
        int w = spl.Panel2.ClientSize.Width;
        int h = spl.Panel2.ClientSize.Height - txtBody.Top;
        if(h<50){h=50;}
        txtBody.Size = new Size(w, h);
    }

    void Form_FormClosing(Object sender, FormClosingEventArgs e)
    {
        if ( !NotSavedYet ){return;} // nothing to do.

        DialogResult result = MessageBox.Show("File is not saved yet.\r\nOverwrite the snippet file?",
            "Confirmation",
            MessageBoxButtons.YesNoCancel,
            MessageBoxIcon.Exclamation,
            MessageBoxDefaultButton.Button3
        );

        if ( result == DialogResult.Yes ) {
            // overwrite the file
            if ( ! SaveSnippetFromListView(GetDefaultSnippetPath()) ) {
                MessageBox.Show("Failed to overwrite the snippet file.");
                e.Cancel = true;
            }
        }
        else if ( result == DialogResult.No ) {
            // nothing to do. form will be closed.
        }
        else if ( result == DialogResult.Cancel ) {
            e.Cancel = true;
        }
    }



    ListViewItem GetSelectedItemOfLsv()
    {
        var indices = lsv.SelectedIndices;
        if ( indices.Count != 1 ) { return null; }
        return lsv.Items[indices[0]];
    }


    void TxtName_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Name = txtName.Text;
        item.SubItems[ColumnIndex_Name].Text = txtName.Text;
    }

    void TxtPrefix_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Prefix = txtPrefix.Text;
        item.SubItems[ColumnIndex_Prefix].Text = ((MyJsonItemInfo)item.Tag).Prefix;
    }

    void TxtDescription_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Description = txtDescription.Text;
        item.SubItems[ColumnIndex_Description].Text = ((MyJsonItemInfo)item.Tag).Description;
    }

    void TxtBody_TextChanged(object sender, EventArgs e)
    {
        if ( _txtChangedByProgram ) { return; }
        NotSavedYet = true;
        ListViewItem item = GetSelectedItemOfLsv();
        ((MyJsonItemInfo)item.Tag).Body = txtBody.Text;
        item.SubItems[ColumnIndex_Body].Text = ((MyJsonItemInfo)item.Tag).BodyFirstLine;
        item.SubItems[ColumnIndex_Lines].Text = ((MyJsonItemInfo)item.Tag).Lines.ToString();

    }

    void Lsv_SelectedIndexChanged(object sender, EventArgs e)
    {
        ListViewItem item = GetSelectedItemOfLsv();
        if(item==null){return;}

        _txtChangedByProgram = true;
        {
            txtName.Text = GetNameFromItem(item);
            txtPrefix.Text = GetPrefixFromItem(item);
            txtBody.Text = GetBodyFromItem(item);
            txtDescription.Text = GetDescciptionFromItem(item);
        }
        _txtChangedByProgram = false;
    }

    void ClearText()
    {
        _txtChangedByProgram = true;
        {
            txtName.Text = "(name)";
            txtPrefix.Text = "(prefix)";
            txtBody.Text = "(body)";
            txtDescription.Text = "(description)";
        }
        _txtChangedByProgram = false;
    }

    bool SaveSnippetFromListView(string path)
    {
        StreamWriter writer = new StreamWriter(path, false);
        try {
            writer.Write(_head);
            writer.Write("\n");

            for ( int i=0 ; i<lsv.Items.Count ; i++ ) {
                ListViewItem item = lsv.Items[i];
                var t = (MyJsonItemInfo)item.Tag;
                writer.Write(t.MakeStringOfJson());
                if (i==lsv.Items.Count-1){
                    writer.Write("\n");
                }
                else{
                    writer.Write(",\n");
                }
            }
            writer.Write(_tail);
        }
        catch ( IOException e ) {
            MessageBox.Show(e.ToString());
            return false;
        }
        finally{
            writer.Close();
        }

        NotSavedYet = false;
        return true;
    }

    // return body
    string ReadAndSeparateSnippetText(string s, out string head, out string tail)
    {
        head = null;
        tail = null;

        Regex rBegin = new Regex(@"^[ \t]*//[ \t]*%%kob58im-ToolBegin%%[ \t]*\r?$", RegexOptions.Multiline);
        Regex rEnd   = new Regex(@"^[ \t]*//[ \t]*%%kob58im-ToolEnd%%[ \t]*\r?$", RegexOptions.Multiline);

        Match mBegin = rBegin.Match(s);
        if ( !mBegin.Success ) {
            MessageBox.Show("Cannot find start mark\r\n// %%kob58im-ToolBegin%%");
            return null;
        }
        Match mEnd   = rEnd.Match(s);
        if ( !mEnd.Success ) {
            MessageBox.Show("Cannot find end mark\r\n// %%kob58im-ToolEnd%%");
            return null;
        }

        int posEndOfBeginMark = mBegin.Groups[0].Captures[0].Index
                               +mBegin.Groups[0].Captures[0].Length;
        int posStartOfEndMark = mEnd.Groups[0].Captures[0].Index;

        head = s.Substring(0, posEndOfBeginMark);
        tail = s.Substring(posStartOfEndMark);
        return s.Substring(posEndOfBeginMark, posStartOfEndMark - posEndOfBeginMark);
    }

    bool LoadSnippetToListView(string path)
    {
        if (!File.Exists(path)) {
            MessageBox.Show("Cannot find file: " + path);
            return false;
        }

        string s;
        try {
            s = File.ReadAllText(path);
        }
        catch ( IOException e ) {
            MessageBox.Show(e.ToString());
            return false;
        }
        string editablePart = ReadAndSeparateSnippetText(s, out _head, out _tail);

        JsonObject joRoot = JsonObject.Parse("{"+editablePart+"}");

        lsv.BeginUpdate();
        try {
            foreach(string name in joRoot.Keys)
            {
                JsonValue jvItem = joRoot.GetNamedValue(name);

                if (jvItem.ValueType == JsonValueType.Object) {
                    JsonObject joItem = jvItem.GetObject();
                    ListViewItem item = MakeItemFromJson(name, joItem);
                    lsv.Items.Add(item);
                }
            }
        }
        finally {
            lsv.EndUpdate();
        }

        return true;
    }

    ListViewItem MakeItemFromJson(string keyName, JsonObject jo)
    {
        var t = new MyJsonItemInfo(keyName, jo);

        string prefix = t.Prefix;
        string[] body = t.BodyAsArray;
        string description = t.Description;

        string firstLine="";
        if (body.Length>=1){firstLine=body[0];}
        var item = new ListViewItem(new string[]{keyName, prefix, body.Length.ToString(), firstLine, description});
        item.Tag = t;
        return item;
    }

    string GetNameFromItem(ListViewItem item) {        return ((MyJsonItemInfo)item.Tag).Name; }
    string GetPrefixFromItem(ListViewItem item) {      return ((MyJsonItemInfo)item.Tag).Prefix; }
    string GetBodyFromItem(ListViewItem item) {        return ((MyJsonItemInfo)item.Tag).Body; }
    string GetDescciptionFromItem(ListViewItem item) { return ((MyJsonItemInfo)item.Tag).Description; }


    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new VscodeSnippetEditor());
    }
}

コンパイル方法

cscのパスは通っている前提。

バッチファイル
csc /r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Runtime.WindowsRuntime\v4.0_4.0.0.0__b77a5c561934e089\system.runtime.windowsruntime.dll ^
/r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Runtime.InteropServices.WindowsRuntime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.InteropServices.WindowsRuntime.dll ^
/r:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Runtime\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Runtime.dll ^
"/r:C:\Program Files (x86)\Windows Kits\8.1\References\CommonConfiguration\Neutral\Annotated\Windows.winmd" %*

入力データ

csharp.json.txt

{
    // Place your snippets for csharp here. Each snippet is defined under a snippet name and has a prefix, body and 
    // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
    // same ids are connected.
    // Example:
    "click event":  { "prefix": "Click",  "body": ["Click  += (sender,e)=>{;};"], "description": "Event lambda expression" },
    "load event":   { "prefix": "Load",   "body": ["Load   += (sender,e)=>{;};"], "description": "Event lambda expression" },
    "resize event": { "prefix": "Resize", "body": ["Resize += (sender,e)=>{;};"], "description": "Event lambda expression" },
    // %%kob58im-ToolBegin%%
    "label constructor": {
        "prefix": "label",
        "body": [
            "$1 lbl = new Label();",
            "lbl.Text = @\"\";",
            "lbl.TextAlign = ContentAlignment.MiddleLeft;",
            "lbl.Location = new Point(,);",
            "lbl.Size = new Size(,);",
            "Controls.Add(lbl);"
        ],
        "description": "label constructor"
    },
    "textbox constructor": {
        "prefix": "textbox",
        "body": [
            "$1 txt = new TextBox();",
            "txt.Text = @\"\";",
            "txt.Location = new Point(,);",
            "txt.Width = ;",
            "Controls.Add(txt);"
        ],
        "description": "textbox constructor"
    },
    "multiline textbox constructor": {
        "prefix": "textboxm",
        "body": [
            "$1 txt = new TextBox();",
            "txt.Multiline = true;",
            "txt.ScrollBars = ScrollBars.Both;",
            "txt.Text = @\"\";",
            "txt.Location = new Point(,);",
            "txt.Size = new Size(,);",
            "Controls.Add(txt);"
        ],
        "description": "multiline textbox constructor"
    },
    "listview constructor": {
        "prefix": ["listview","hoge"],
        "body": [
            "$1 lsv = new ListView();",
            "lsv.View = View.Details;",
            "lsv.FullRowSelect = true;",
            "lsv.HideSelection = false;",
            "lsv.MultiSelect = false;",
            "lsv.GridLines = true;",
            "lsv.Columns.Add(\"Item\",  100, HorizontalAlignment.Left);",
            "lsv.Columns.Add(\"Value\", 400, HorizontalAlignment.Left);",
            "lsv.Location = new Point(,);",
            "lsv.Size = new Size(,);",
            "//lsv.Dock = DockStyle.Fill;",
            "Controls.Add(lsv);"
        ],
        "description": "listview constructor"
    },
    "task": {
        "prefix": "Task",
        "body": [
            "Task.Run(() => {",
            "",
            "    // this.Invoke((MethodInvoker)(() => xxx.yyy = zzz)); // UI control",
            "",
            "});"
        ],
        "description": "Task"
    },
    "using directive": {
        "prefix": "using",
        "body": [
            "using "
        ],
        "description": "using directive"
    },
    "CSharp Template": {
        "prefix": "usingall",
        "body": [
            "using System;",
            "using System.Collections;",
            "using System.Collections.Generic;",
            "using System.ComponentModel;",
            "using System.Data;",
            "using System.Diagnostics;",
            "using System.Drawing;",
            "using System.Drawing.Drawing2D;",
            "using System.Drawing.Imaging;",
            "using System.IO;",
            "using System.Reflection;",
            "using System.Runtime.InteropServices;",
            "using System.Text;",
            "using System.Text.RegularExpressions;",
            "using System.Threading;",
            "using System.Threading.Tasks;",
            "using System.Windows.Forms;",
            "",
            "class XXXX : Form",
            "{",
            "    XXXX()",
            "    {",
            "        //Text = ;",
            "        //ClientSize = new Size(,);",
            "        //Controls.Add(xx);",
            "    }",
            "    ",
            "    [STAThread]",
            "    static void Main(string[] args)",
            "    {",
            "        Application.Run(new XXXX());",
            "    }",
            "}",
            ""
        ],
        "description": "Insert the template code"
    },
    "CSharp Template2": {
        "prefix": "usingconsole",
        "body": [
            "using System;",
            "using System.Collections;",
            "using System.Collections.Generic;",
            "using System.IO;",
            "using System.Text;",
            "using System.Text.RegularExpressions;",
            "",
            "class XXXX",
            "{",
            "    [STAThread]",
            "    static void Main(string[] args)",
            "    {",
            "        ;",
            "    }",
            "}",
            ""
        ],
         "description": "Insert the template code"
    }
    // %%kob58im-ToolEnd%%
}

  1. //コメントとか ↩

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# - Windows10のログイン画面の壁紙をサムネイル表示してドラッグ&ドロップでエクスプローラにコピーするツール作ってみた

ロック画面の画像(参考サイト#1を参照ください)をサムネイル表示して、欲しい画像だけ簡単にコピーできるようにしたいと思ったのでC#で作ってみた。

画面キャプチャ

image.png

使い方

画像を選んでデスクトップ(他のフォルダでも可)にドラッグ&ドロップすると、ファイルがコピーされます。
コピー後の拡張子の変更(.jpgにしてください)は手動でお願いします。(一時ファイルを作るのが面倒なため)

ソースコード

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;

class WinWallpaperPreview : Form
{
    static readonly string imageDir = GetDefaultWallpaperPath();

    static string GetDefaultWallpaperPath()
    {
        return Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"),
                             @"Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets");
    }

    class ImageItemInfo
    {
        public string FullPath{get; private set;}
        public System.Drawing.Size Size{get; private set;}
        public int Width {get{return Size.Width;}}
        public int Height {get{return Size.Height;}}

        public ImageItemInfo(string fullPath, Size size) {
            FullPath = fullPath;
            Size = size;
        }
    }

    // 幅w、高さhのImageオブジェクトを作成
    Image CreateThumbnail(Image image, Size size)
    {
        int w = size.Width;
        int h = size.Height;
        Bitmap canvas = new Bitmap(w, h);

        Graphics g = Graphics.FromImage(canvas);
        g.FillRectangle(Brushes.LightGray, 0, 0, w, h);

        float fw = (float)w / (float)image.Width;
        float fh = (float)h / (float)image.Height;

        float scale = Math.Min(fw, fh);
        fw = image.Width * scale;
        fh = image.Height * scale;

        g.DrawImage(image, (w - fw) / 2, (h - fh) / 2, fw, fh);
        g.Dispose();

        return canvas;
    }

    void LoadImage()
    {
        string[] files = Directory.GetFiles(imageDir);
        int count = 0;

        foreach (string filePath in files)
        {
            Image original;
            try {
                original = Image.FromFile(filePath); // 拡張子がなくても自動判別してくれる
            }
            catch ( Exception ex ) { // かなり乱暴なのでマネしないように...
                Console.WriteLine(ex);
                Console.WriteLine("Ignored: " + Path.GetFileName(filePath));
                continue;
            }
            if ( original == null ) {
                Console.WriteLine("Ignored: " + Path.GetFileName(filePath));
                continue;
            }

            Image thumbnail = CreateThumbnail(original, imgList.ImageSize);
            imgList.Images.Add(thumbnail);
            string text = original.Size.Width.ToString() + " x " + original.Size.Height.ToString();
            lsv.Items.Add(text, count);
//            lsv.Items.Add(Path.GetFileName(filePath), count);
            lsv.Items[count].Tag = new ImageItemInfo(filePath, original.Size);
            count++;

            original.Dispose();
            thumbnail.Dispose();
        }
    }

    //void Lsv_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
    //{
    //    Console.WriteLine(e.Action);
    //}

    void Lsv_ItemDrag(Object sender, ItemDragEventArgs e)
    {
        var item = (ListViewItem)(e.Item);
        string path = Path.Combine(imageDir, ((ImageItemInfo)(item.Tag)).FullPath);
        //ドラッグ&ドロップ処理を開始する
        IDataObject iData = new DataObject(DataFormats.FileDrop, new string[]{path});
        lsv.DoDragDrop(iData, DragDropEffects.Copy);
    }


    /*
    壁紙変更は下記でやれるらしい
    [DllImport("User32.DLL")]
    public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, string pvParam, uint fWinIni);
    public static uint SPI_SETDESKWALLPAPER = 0x0014;

    String sName = fullPath;
    SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, sName, 0);
    */

    ListView lsv;
    ImageList imgList;
    //StatusStrip sts;

    WinWallpaperPreview()
    {
        int width = 100;
        int height = 80;

        imgList = new ImageList();
        imgList.ImageSize = new Size(width, height);

        lsv = new ListView();
        lsv.View = View.Tile;
        lsv.LargeImageList = imgList;
        lsv.Dock = DockStyle.Fill;
        lsv.MultiSelect = false;
        //lsv.SelectedIndexChanged += ;
        lsv.ItemDrag += Lsv_ItemDrag;
        //lsv.QueryContinueDrag += Lsv_QueryContinueDrag;
        Controls.Add(lsv);

        //sts = new StatusStrip();
        //Controls.Add(sts);

        Load+=(s,e)=>{LoadImage();};

        Text = "Wallpaper Preview";
        ClientSize = new Size(700,500);
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new WinWallpaperPreview());
    }
}

参考サイト

  1. Windows10のロック画面の画像がキレイだから壁紙のスライドショーにしたいと思った - Qiita
  2. ListViewコントロールでサムネイル画像を一覧表示するには? - @IT
  3. 異種アプリへドラッグアンドドロップする方法 - C# と VB.NET の質問掲示板
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# - Windows10のログイン画面の壁紙をサムネイル表示してドラッグ&ドロップでエクスプローラにコピーする

ロック画面の画像(参考サイト#1を参照ください)をサムネイル表示して、欲しい画像だけ簡単にコピーできるようにしたいと思ったのでC#で作ってみた。

画面キャプチャ

image.png

使い方

画像を選んでデスクトップ(他のフォルダでも可)にドラッグ&ドロップすると、ファイルがコピーされます。
コピー後の拡張子の変更(.jpgにしてください)は手動でお願いします。(一時ファイルを作るのが面倒なため)

ソースコード

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;

class WinWallpaperPreview : Form
{
    static readonly string imageDir = GetDefaultWallpaperPath();

    static string GetDefaultWallpaperPath()
    {
        return Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"),
                             @"Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets");
    }

    class ImageItemInfo
    {
        public string FullPath{get; private set;}
        public System.Drawing.Size Size{get; private set;}
        public int Width {get{return Size.Width;}}
        public int Height {get{return Size.Height;}}

        public ImageItemInfo(string fullPath, Size size) {
            FullPath = fullPath;
            Size = size;
        }
    }

    // 幅w、高さhのImageオブジェクトを作成
    Image CreateThumbnail(Image image, Size size)
    {
        int w = size.Width;
        int h = size.Height;
        Bitmap canvas = new Bitmap(w, h);

        Graphics g = Graphics.FromImage(canvas);
        g.FillRectangle(Brushes.LightGray, 0, 0, w, h);

        float fw = (float)w / (float)image.Width;
        float fh = (float)h / (float)image.Height;

        float scale = Math.Min(fw, fh);
        fw = image.Width * scale;
        fh = image.Height * scale;

        g.DrawImage(image, (w - fw) / 2, (h - fh) / 2, fw, fh);
        g.Dispose();

        return canvas;
    }

    void LoadImage()
    {
        string[] files = Directory.GetFiles(imageDir);
        int count = 0;

        foreach (string filePath in files)
        {
            Image original;
            try {
                original = Image.FromFile(filePath); // 拡張子がなくても自動判別してくれる
            }
            catch ( Exception ex ) { // かなり乱暴なのでマネしないように...
                Console.WriteLine(ex);
                Console.WriteLine("Ignored: " + Path.GetFileName(filePath));
                continue;
            }
            if ( original == null ) {
                Console.WriteLine("Ignored: " + Path.GetFileName(filePath));
                continue;
            }

            Image thumbnail = CreateThumbnail(original, imgList.ImageSize);
            imgList.Images.Add(thumbnail);
            string text = original.Size.Width.ToString() + " x " + original.Size.Height.ToString();
            lsv.Items.Add(text, count);
//            lsv.Items.Add(Path.GetFileName(filePath), count);
            lsv.Items[count].Tag = new ImageItemInfo(filePath, original.Size);
            count++;

            original.Dispose();
            thumbnail.Dispose();
        }
    }

    //void Lsv_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
    //{
    //    Console.WriteLine(e.Action);
    //}

    void Lsv_ItemDrag(Object sender, ItemDragEventArgs e)
    {
        var item = (ListViewItem)(e.Item);
        string path = Path.Combine(imageDir, ((ImageItemInfo)(item.Tag)).FullPath);
        //ドラッグ&ドロップ処理を開始する
        IDataObject iData = new DataObject(DataFormats.FileDrop, new string[]{path});
        lsv.DoDragDrop(iData, DragDropEffects.Copy);
    }


    /*
    壁紙変更は下記でやれるらしい
    [DllImport("User32.DLL")]
    public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, string pvParam, uint fWinIni);
    public static uint SPI_SETDESKWALLPAPER = 0x0014;

    String sName = fullPath;
    SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, sName, 0);
    */

    ListView lsv;
    ImageList imgList;
    //StatusStrip sts;

    WinWallpaperPreview()
    {
        int width = 100;
        int height = 80;

        imgList = new ImageList();
        imgList.ImageSize = new Size(width, height);

        lsv = new ListView();
        lsv.View = View.Tile;
        lsv.LargeImageList = imgList;
        lsv.Dock = DockStyle.Fill;
        lsv.MultiSelect = false;
        //lsv.SelectedIndexChanged += ;
        lsv.ItemDrag += Lsv_ItemDrag;
        //lsv.QueryContinueDrag += Lsv_QueryContinueDrag;
        Controls.Add(lsv);

        //sts = new StatusStrip();
        //Controls.Add(sts);

        Load+=(s,e)=>{LoadImage();};

        Text = "Wallpaper Preview";
        ClientSize = new Size(700,500);
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new WinWallpaperPreview());
    }
}

参考サイト

  1. Windows10のロック画面の画像がキレイだから壁紙のスライドショーにしたいと思った - Qiita
  2. ListViewコントロールでサムネイル画像を一覧表示するには? - @IT
  3. 異種アプリへドラッグアンドドロップする方法 - C# と VB.NET の質問掲示板
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WPF バインディング

準備

対象

C#なんとなく分かってる。駆け出しプログラマー

目標

簡単なバインディングの流れをつかむ。なので、検索機能のみあるものを作る。
バインディング主軸で説明するので、SQLなどライブラリとかは使いません。

どのようなアプリにするか?

生徒のテストの成績を管理するアプリ。

要件

生徒はクラスIdと、出席番号と、男か女と、点数を持つ。
検索、追加、削除を行えるようにする。

設計

生徒一人の状態を表すStudentクラスを作成する。
WPFで、Xaml側への反映を行うためViewModelクラスの作成を行う。
Studentのフィールドは

変数名 型 説明
ClassId int クラスの番号
Id int 出席番号
Gender bool 男か女か
Score int 点数

ViewModelのフィールドは

変数名 型 説明
ScoreList ObservableCollection<Student> 生徒全体のリスト
ResultList ObservableCollection<Student> 検索結果のリスト
ClassId int クラスの番号(検索用)
Id int 出席番号(検索用)
IsManChecked bool 男がチェックされているか
IsWomanChecked bool 女がチェックされているか
Score int 点数(検索用)

コード作成

Studentクラス

MainWindow.xaml.cs
public class Student
{
    /// <summary>
    /// クラスの番号
    /// </summary>
    public int ClassId { get; set; }

    /// <summary>
    /// 出席番号
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 男か女か
    /// </summary>
    public bool Gender { get; set; }

    /// <summary>
    /// 点数
    /// </summary>
    public int Score { get; set; }
}

MainWindowのVMクラスMainVM

MainWindow.xaml.cs
public class MainVM
{
    public ObservableCollection<Student> ScoreList { get; set; }   = new ObservableCollection<Student>();
    public ObservableCollection<Student> ResultList { get; set; } = new ObservableCollection<Student>();
    private int _ClassId;
    /// <summary>
    /// クラスの番号(検索用)
    /// </summary>
    public int ClassId 
    {
        get => _ClassId;
        set
        {
            _ClassId = value;
        } 
    }

    private int _Id;
    /// <summary>
    /// 出席番号(検索用)
    /// </summary>
    public int Id
    {
        get => _Id;
        set
        {
            _Id = value;
        } 
    }

    private bool _IsManChecked;
    /// <summary>
    /// 男がチェックされているか
    /// </summary>
    public bool IsManChecked
    {
        get => _IsManChecked;
        set
        {
            _IsManChecked = value;
        }
    }

    private bool _IsWomanChecked;
    /// <summary>
    /// 女がチェックされているか
    /// </summary>
    public bool IsWomanChecked
    {
        get => _IsWomanChecked;
        set
        {
            _IsWomanChecked = value;
        }
    }

    private int _Score;
    /// <summary>
    /// 点数(検索用)
    /// </summary>
    public int Score 
    {
        get => _Score;
        set
        {
            _Score = value;
        }
    }
}

Studentクラスの方は入れ物としての役割なのでこれでいいだろう。
VMクラスではまだ色々と準備が必要になる。説明の後に記述する。

説明:なぜカプセル化を行うのか?

VMクラスでなぜprivateのカプセル化を行っているかというと、
バインディングを行うには通知を送る必要がある。VMクラスで値が代入された瞬間に通知を行うとなるとsetterに入れるのがいいが、setterに入れるとなると値の代入はコードで明示的に示してあげないと代入されてくれなくなる。
なので、明示的にClassId = valueのようにしようとするとこれがまた代入とみなされsetterが動いてしまい、無限ループのようになってエラーが起こってしまう。
なのでsetterに代入の処理を書いてあげるときはカプセル化するのがよい。

説明:なぜバインディングするときにリストを使ってはいけないのか?

上の方にあるObservableCollectionはリストのようなもの。
なぜリストを使わずにこちらを使うのかというと、intやboolであれば代入=状態の変更なので代入のときに変更通知を出してあげれば、状態変更したら変更通知が出されたことになる。
しかし、Listなどの場合Add,Removeなどがあるため代入≠状態の変更なので、代入の時に、変更通知を出してあげればいいというだけでは十分ではない。
ObservableCollectionはAdd,Removeなどしたときに、変更通知を出してくれるクラスである。

INotifyPropertyChanged実装

次にバインディングを行うためのINotifyPropertyChangedインターフェースをVMクラスに実装する。

MainVMクラスを改良

MainWindow.xaml.cs
public class MainVM:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string PropertyName)
    {
        var e = new PropertyChangedEventArgs(PropertyName);
        PropertyChanged?.Invoke(this, e);
    }

    public ObservableCollection<Student> ScoreList { get; set; } = new ObservableCollection<Student>();
    public ObservableCollection<Student> ResultList { get; set; } = new ObservableCollection<Student>();


    private int _ClassId;
    /// <summary>
    /// クラスの番号(検索用)
    /// </summary>
    public int ClassId 
    {
        get => _ClassId;
        set
        {
            _ClassId = value;
            NotifyPropertyChanged(nameof(ClassId));
        } 
    }

    private int _Id;
    /// <summary>
    /// 出席番号(検索用)
    /// </summary>
    public int Id
    {
        get => _Id;
        set
        {
            _Id = value;
            NotifyPropertyChanged(nameof(Id));
        } 
    }

    private bool _IsManChecked;
    /// <summary>
    /// 男がチェックされているか
    /// </summary>
    public bool IsManChecked
    {
        get => _IsManChecked;
        set
        {
            _IsManChecked = value;
            NotifyPropertyChanged(nameof(IsManChecked));
        }
    }

    private bool _IsWomanChecked;
    /// <summary>
    /// 女がチェックされているか
    /// </summary>
    public bool IsWomanChecked
    {
        get => _IsWomanChecked;
        set
        {
            _IsWomanChecked = value;
            NotifyPropertyChanged(nameof(IsWomanChecked));
        }
    }

    private int _Score;



    /// <summary>
    /// 点数(検索用)
    /// </summary>
    public int Score 
    {
        get => _Score;
        set
        {
            _Score = value;
            NotifyPropertyChanged(nameof(Score));
        }
    }
}

InofiryPropertyChangedはただ一つPropertyChangedだけ持っており、それを実装する。
PropertyChanged.Invoke(object, PropertyChangedEventArgs)でPropertyChangedイベントを実行することができobjectはthisで済ませればよく、PropertyChangedEventArgsはnew PropertyChangedEventArgs(PropertyName)のように変数名を入れて宣言してあげればよい。
変数名に関して、間違いが起こらないようにnameof演算子で実行する。
そしてメソッド化したものをlist以外の各変数のsetterに定義する。

そしてMainWindowクラスでVMクラスをグローバル変数にして、DataContextに登録してあげる。

MainWindow.xaml.cs
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private MainVM MyVM = new MainVM();

    public MainWindow()
    {
        InitializeComponent();
        DataContext = MyVM;
    }
}

初期値代入

これでバインディングの準備は完了したがバインディングが成功しているかどうかわかりづらいのでMainVMクラスのカプセル化した変数とリストにそれぞれ初期値を入れてあげる。

MainWindow.xaml.cs
public class MainVM:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(string PropertyName)
    {
        var e = new PropertyChangedEventArgs(PropertyName);
        PropertyChanged?.Invoke(this, e);
    }

    public ObservableCollection<Student> ScoreList { get; set; } = new ObservableCollection<Student>()
    {
        new Student(){ClassId = 1, Id = 1, Gender = true, Score = 82},
        new Student(){ClassId = 1, Id = 2, Gender = false, Score = 89},
        new Student(){ClassId = 1, Id = 3, Gender = true, Score = 74},
        new Student(){ClassId = 2, Id = 1, Gender = false, Score = 79},
        new Student(){ClassId = 2, Id = 2, Gender = true, Score = 94},
        new Student(){ClassId = 2, Id = 3, Gender = false, Score = 87},
        new Student(){ClassId = 3, Id = 1, Gender = true, Score = 69},
        new Student(){ClassId = 3, Id = 2, Gender = false, Score = 75},
        new Student(){ClassId = 3, Id = 3, Gender = true, Score = 94}
    };
    public ObservableCollection<Student> ResultList { get; set; } = new ObservableCollection<Student>();

    private int _ClassId = 1;
    /// <summary>
    /// クラスの番号(検索用)
    /// </summary>
    public int ClassId 
    {
        get => _ClassId;
        set
        {
            _ClassId = value;
            NotifyPropertyChanged(nameof(ClassId));
        } 
    }

    private int _Id = 1;
    /// <summary>
    /// 出席番号(検索用)
    /// </summary>
    public int Id
    {
        get => _Id;
        set
        {
            _Id = value;
            NotifyPropertyChanged(nameof(Id));
        } 
    }

    private bool _IsManChecked = true;
    /// <summary>
    /// 男がチェックされているか
    /// </summary>
    public bool IsManChecked
    {
        get => _IsManChecked;
        set
        {
            _IsManChecked = value;
            NotifyPropertyChanged(nameof(IsManChecked));
        }
    }

    private bool _IsWomanChecked = true;
    /// <summary>
    /// 女がチェックされているか
    /// </summary>
    public bool IsWomanChecked
    {
        get => _IsWomanChecked;
        set
        {
            _IsWomanChecked = value;
            NotifyPropertyChanged(nameof(IsWomanChecked));
        }
    }

    private int _Score = 0;



    /// <summary>
    /// 点数(検索用)
    /// </summary>
    public int Score 
    {
        get => _Score;
        set
        {
            _Score = value;
            NotifyPropertyChanged(nameof(Score));
        }
    }
}

これで初期値が入ったので、画面側のxamlを作る。

MainWindow.xaml
<Window x:Class="WpfApp3.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:WpfApp3"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="クラス"></TextBlock>
                <TextBox Text="{Binding ClassId}"></TextBox>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="出席番号"></TextBlock>
                <TextBox Text="{Binding Id}"></TextBox>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <CheckBox Content="男" IsChecked="{Binding IsManChecked}"></CheckBox>
                <CheckBox Content="女" IsChecked="{Binding IsWomanChecked}"></CheckBox>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="点数"></TextBlock>
                <TextBox Text="{Binding Score}"></TextBox>
            </StackPanel>
            <Button Content="検索" Click="Button_Click"></Button>
        </StackPanel>
        <StackPanel Orientation="Vertical" HorizontalAlignment="Right">
            <DataGrid ItemsSource="{Binding ScoreList}"></DataGrid>
            <DataGrid ItemsSource="{Binding ResultList}"></DataGrid>
        </StackPanel>
    </Grid>
</Window>

コード側全体

MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace WpfApp3
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MainVM MyVM = new MainVM();

        public MainWindow()
        {
            InitializeComponent();
            DataContext = MyVM;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //まだ未実装
        }
    }

    public class MainVM:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string PropertyName)
        {
            var e = new PropertyChangedEventArgs(PropertyName);
            PropertyChanged?.Invoke(this, e);
        }

        public ObservableCollection<Student> ScoreList { get; set; } = new ObservableCollection<Student>()
        {
            new Student(){ClassId = 1, Id = 1, Gender = true, Score = 82},
            new Student(){ClassId = 1, Id = 2, Gender = false, Score = 89},
            new Student(){ClassId = 1, Id = 3, Gender = true, Score = 74},
            new Student(){ClassId = 2, Id = 1, Gender = false, Score = 79},
            new Student(){ClassId = 2, Id = 2, Gender = true, Score = 94},
            new Student(){ClassId = 2, Id = 3, Gender = false, Score = 87},
            new Student(){ClassId = 3, Id = 1, Gender = true, Score = 69},
            new Student(){ClassId = 3, Id = 2, Gender = false, Score = 75},
            new Student(){ClassId = 3, Id = 3, Gender = true, Score = 94}
        };

        public ObservableCollection<Student> ResultList { get; set; } = new ObservableCollection<Student>();

        private int _ClassId = 1;
        /// <summary>
        /// クラスの番号(検索用)
        /// </summary>
        public int ClassId 
        {
            get => _ClassId;
            set
            {
                _ClassId = value;
                NotifyPropertyChanged(nameof(ClassId));
            } 
        }

        private int _Id = 1;
        /// <summary>
        /// 出席番号(検索用)
        /// </summary>
        public int Id
        {
            get => _Id;
            set
            {
                _Id = value;
                NotifyPropertyChanged(nameof(Id));
            } 
        }

        private bool _IsManChecked = true;
        /// <summary>
        /// 男がチェックされているか
        /// </summary>
        public bool IsManChecked
        {
            get => _IsManChecked;
            set
            {
                _IsManChecked = value;
                NotifyPropertyChanged(nameof(IsManChecked));
            }
        }

        private bool _IsWomanChecked = true;
        /// <summary>
        /// 女がチェックされているか
        /// </summary>
        public bool IsWomanChecked
        {
            get => _IsWomanChecked;
            set
            {
                _IsWomanChecked = value;
                NotifyPropertyChanged(nameof(IsWomanChecked));
            }
        }

        private int _Score = 0;



        /// <summary>
        /// 点数(検索用)
        /// </summary>
        public int Score 
        {
            get => _Score;
            set
            {
                _Score = value;
                NotifyPropertyChanged(nameof(Score));
            }
        }
    }

    public class Student
    {
        /// <summary>
        /// クラスの番号
        /// </summary>
        public int ClassId { get; set; }

        /// <summary>
        /// 出席番号
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 男か女か
        /// </summary>
        public bool Gender { get; set; }

        /// <summary>
        /// 点数
        /// </summary>
        public int Score { get; set; }
    }
}

実行結果
wpf4.PNG

検索機能

ここから検索機能を追加する。
MainWindowクラスButton_Clickメソッドの部分

MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
    var linq = new ObservableCollection<Student>(MyVM.ScoreList).AsEnumerable();

    if(MyVM.ClassId != 0)
    {
        linq = linq.Where(x => x.ClassId == MyVM.ClassId);
    }

    if(MyVM.Id != 0)
    {
        linq = linq.Where(x => x.Id == x.Id);
    }

    //三項演算子,x.GenderがtrueであればIsMancheckedをみて、falseであればIsWomanCheckを見る。
    linq = linq.Where(x => x.Gender?MyVM.IsManChecked:MyVM.IsWomanChecked);

    if(MyVM.Score >= 0)
    {
        linq = linq.Where(x => x.Score >= MyVM.Score);
    }

    MyVM.ResultList = new ObservableCollection<Student>(linq);
    MyVM.NotifyPropertyChanged(nameof(MyVM.ResultList));
}

検索の大体はLinqでやっているので特にいうこともないが、最後のMyVM.NotifyPropertyChangedについて
ObservableCollection型の時はAdd,Removeは変更通知を出してくれるが、代入の時は変更通知を出してくれないので自分でやる必要がある。
ただし、intやboolと同じようにprivateにしてカプセル化を行うとAdd,Removeなどをした場合に状態が想定したものと一致しなくなるのでカプセル化をやるのは難しそうではある。
そこで、MainWindowクラスで直接呼び出して変更通知を行い反映させてやる。
(きちんとやればVMクラスで完結するはず。)

全コード

xaml

MainWindow.xaml
<Window x:Class="WpfApp3.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:WpfApp3"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="クラス"></TextBlock>
                <TextBox Text="{Binding ClassId}"></TextBox>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="出席番号"></TextBlock>
                <TextBox Text="{Binding Id}"></TextBox>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <CheckBox Content="男" IsChecked="{Binding IsManChecked}"></CheckBox>
                <CheckBox Content="女" IsChecked="{Binding IsWomanChecked}"></CheckBox>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="点数"></TextBlock>
                <TextBox Text="{Binding Score}"></TextBox>
            </StackPanel>
            <Button Content="検索" Click="Button_Click"></Button>
        </StackPanel>
        <StackPanel Orientation="Vertical" HorizontalAlignment="Right">
            <DataGrid ItemsSource="{Binding ScoreList}"></DataGrid>
            <DataGrid ItemsSource="{Binding ResultList}"></DataGrid>
        </StackPanel>
    </Grid>
</Window>

C#側のコード

MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;

namespace WpfApp3
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MainVM MyVM = new MainVM();

        public MainWindow()
        {
            InitializeComponent();
            DataContext = MyVM;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var linq = new ObservableCollection<Student>(MyVM.ScoreList).AsEnumerable();

            if(MyVM.ClassId != 0)
            {
                linq = linq.Where(x => x.ClassId == MyVM.ClassId);
            }

            if(MyVM.Id != 0)
            {
                linq = linq.Where(x => x.Id == x.Id);
            }

            //三項演算子,x.GenderがtrueであればIsMancheckedをみて、falseであればIsWomanCheckを見る。
            linq = linq.Where(x => x.Gender?MyVM.IsManChecked:MyVM.IsWomanChecked);

            if(MyVM.Score >= 0)
            {
                linq = linq.Where(x => x.Score >= MyVM.Score);
            }

            MyVM.ResultList = new ObservableCollection<Student>(linq);
            MyVM.NotifyPropertyChanged(nameof(MyVM.ResultList));
        }
    }

    public class MainVM:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string PropertyName)
        {
            var e = new PropertyChangedEventArgs(PropertyName);
            PropertyChanged?.Invoke(this, e);
        }

        public ObservableCollection<Student> ScoreList { get; set; } = new ObservableCollection<Student>()
        {
            new Student(){ClassId = 1, Id = 1, Gender = true, Score = 82},
            new Student(){ClassId = 1, Id = 2, Gender = false, Score = 89},
            new Student(){ClassId = 1, Id = 3, Gender = true, Score = 74},
            new Student(){ClassId = 2, Id = 1, Gender = false, Score = 79},
            new Student(){ClassId = 2, Id = 2, Gender = true, Score = 94},
            new Student(){ClassId = 2, Id = 3, Gender = false, Score = 87},
            new Student(){ClassId = 3, Id = 1, Gender = true, Score = 69},
            new Student(){ClassId = 3, Id = 2, Gender = false, Score = 75},
            new Student(){ClassId = 3, Id = 3, Gender = true, Score = 94}
        };

        public ObservableCollection<Student> ResultList { get; set; } = new ObservableCollection<Student>();

        private int _ClassId = 0;
        /// <summary>
        /// クラスの番号(検索用)
        /// </summary>
        public int ClassId 
        {
            get => _ClassId;
            set
            {
                _ClassId = value;
                NotifyPropertyChanged(nameof(ClassId));
            } 
        }

        private int _Id = 0;
        /// <summary>
        /// 出席番号(検索用)
        /// </summary>
        public int Id
        {
            get => _Id;
            set
            {
                _Id = value;
                NotifyPropertyChanged(nameof(Id));
            } 
        }

        private bool _IsManChecked = true;
        /// <summary>
        /// 男がチェックされているか
        /// </summary>
        public bool IsManChecked
        {
            get => _IsManChecked;
            set
            {
                _IsManChecked = value;
                NotifyPropertyChanged(nameof(IsManChecked));
            }
        }

        private bool _IsWomanChecked = true;
        /// <summary>
        /// 女がチェックされているか
        /// </summary>
        public bool IsWomanChecked
        {
            get => _IsWomanChecked;
            set
            {
                _IsWomanChecked = value;
                NotifyPropertyChanged(nameof(IsWomanChecked));
            }
        }

        private int _Score = 0;



        /// <summary>
        /// 点数(検索用)
        /// </summary>
        public int Score 
        {
            get => _Score;
            set
            {
                _Score = value;
                NotifyPropertyChanged(nameof(Score));
            }
        }
    }

    public class Student
    {
        /// <summary>
        /// クラスの番号
        /// </summary>
        public int ClassId { get; set; }

        /// <summary>
        /// 出席番号
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 男か女か
        /// </summary>
        public bool Gender { get; set; }

        /// <summary>
        /// 点数
        /// </summary>
        public int Score { get; set; }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む