20200414のC#に関する記事は10件です。

[Unity初心者]マウスポインタの位置を取得

概要

マウスをクリックした時に、マウスポインタの座標をコンソールに表示します。

本文

    void Update()
    {
        if (Input.GetMouseButtonDown(0)) {
            Vector3 mousePos = Input.mousePosition;
            Debug.Log("x:"+mousePos.x+"    y:"+mousePos.y);
        } 
    }

Vector3型は
~.xでX座標
~.yでY座標
~.zでZ座標が取り出せます

スクリーンショット (73).png

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

string配列をint配列に変換

string配列をint配列へできるだけスッキリと記述したかったので
ググった結果をメモメモ(~_~メ)

string[] strArray = { "1", "10", "100", "1000", "10000" };
int[] intArray = new int[strArray.Length];
for (int i = 0; i < intArray.Length; i++)
intArray[i] = int.Parse(strArray[i]);

上記をスッキリと。。。

・LINQで
int[] intArray = strArray
.Select(int.Parse)
.ToArray();

・ArrayクラスのConvertAll()メソッドで
int[] intArray = Array.ConvertAll(strArray, int.Parse);

・Listとfreachを使用して
var intList = new List(strArray.Length);
foreach(var s in strArray)
intList.Add(int.Parse(s));
var intArray = intList.ToArray();

もっといい書き方があればぜひコメントへお願いいたします(^_^

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

Panelなどコントロール(Control)上で右クリックをしてコンテキストメニューを開いた位置に新たにPictureBoxなどのコントロールを追加する(その2:追加したコントロールを削除する))

 (その1)で紹介した「Panelなどコントロール(Control)上で右クリックをしてコンテキストメニューを開いた位置に新たにPictureBoxなどのコントロールを追加する」に引き続き、更に追加したコントロールを削除するソースコードを紹介する。

 なお、削除の方法は、フォーム上にあるコントロールを、右クリックでコンテキストメニューを表示して、フォーム上から動的に削除する(その1)(その2:削除したいコントロールが複数ある場合)で紹介したので、参照していただきたい。

動作画像.png

// 1.コントロール(panel)上で左クリックしたときにコンテキストメニューを開き、コンテキストメニューを開いた時にあったマウスの先端に動的に新たにコントロールを作る。
// 2.新たに作ったコントロールは、左クリックによりコンテキストメニューを開き削除できる。
// 参考資料
// https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.contextmenustrip?view=netframework-4.8

// https://dobon.net/vb/dotnet/control/cmclickpoint.html

using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;

namespace TestSourceControl
{
    class Form3 : Form
    {
        public static void Main()
        {
            Application.Run(new Form3());
        }
        private Panel panel;

        // panelを左クリックしたときのContextMenuStripコントロールの宣言
        private ContextMenuStrip contextMenuStripOnPanel;
        private ContextMenuStrip contextMenuStripOnAdditionalControl;

        public Form3()
        {
            this.Size = new Size(800, 800);

            // panelのコンテキストメニューを宣言とコンテキストメニューを開いた時のイベントハンドラーの定義
            contextMenuStripOnPanel = new ContextMenuStrip();

            contextMenuStripOnPanel.Opening += new CancelEventHandler(cms_Opening); // 必ずForm3()で宣言する

            panel = new Panel();
            panel.Location = new Point(100, 100);
            panel.Size = new Size(600, 600);
            panel.BorderStyle = BorderStyle.FixedSingle;
            panel.BackColor = Color.SkyBlue;
            panel.Parent = this;
            panel.ContextMenuStrip = contextMenuStripOnPanel;

            Label labelOnForm = new Label();
            labelOnForm.Text = " 水色のパネルの上で、右クリックをして、" + "\r\n" + "コンテキストメニューからコントロールを選択すると、" + "\r\n" + "パネル上の右クリックした際のマウスポインタの先の位置に、選択したコントロールを追加できます。" + "\r\n" + " またその追加したコントロールの上で左クリックをすると削除できます。";
            labelOnForm.AutoSize = true; ;
            labelOnForm.Location = new Point(0, 0);
            labelOnForm.Parent = this;

            contextMenuStripOnAdditionalControl = new ContextMenuStrip();   // 必ずForm3()で宣言する

            // 追加したコントロールを左クリックした時に表示するコンテキストメニュー(contextMenuStripOnAdditionalControlMenuItems()に記述したものと同じ。メニューの内容を共通にするなら以下のコメントアウトを外す
            //contextMenuStripOnAdditionalControl.Items.Clear();
            //contextMenuStripOnAdditionalControl.Items.Add("削除");
            //contextMenuStripOnAdditionalControl.Items.Add("追加コンソールのメニューアイテム2");
            //contextMenuStripOnAdditionalControl.Items.Add("追加コンソールのメニューアイテム3");
            //contextMenuStripOnAdditionalControl.Items[0].Click += AdditionalControlItems0_Click;
            //contextMenuStripOnAdditionalControl.Items[1].Click += AdditionalControlItems1_Click;
            //contextMenuStripOnAdditionalControl.Items[2].Click += AdditionalControlItems2_Click;
            // ここまで
        }

        private Point cp;
        void cms_Opening(object sender, CancelEventArgs e)
        {
            ContextMenuStrip menu = (ContextMenuStrip)sender;
            //マウスカーソルの位置を画面座標で取得
            Point mp = MousePosition;
            //ContextMenuStripを表示しているコントロールのクライアント座標に変換
            cp = menu.SourceControl.PointToClient(mp);
            //結果を表示
            Console.WriteLine(cp);

            // コンテキストメニューの表示
            contextMenuStripOnPanel.Items.Clear();
            contextMenuStripOnPanel.Items.Add("ピクチャーボックスを追加");
            contextMenuStripOnPanel.Items.Add("テキストボックスを追加");
            contextMenuStripOnPanel.Items.Add("ラベルを追加");

            contextMenuStripOnPanel.Items[0].Click += PanelMenuItems0_Click;
            contextMenuStripOnPanel.Items[1].Click += PanelMenuItems1_Click;
            contextMenuStripOnPanel.Items[2].Click += PanelMenuItems2_Click;

            // キャンセルをfalseに設定します。(イベントをキャンセルするかどうかの設定)
            // 空のエントリに基づいてtrueに最適化されます。
            e.Cancel = false;   //trueにするとコンテキスメニューが表示されない
        }




        // PictureBoxをつくるイベントハンドラ
        private void PanelMenuItems0_Click(object sender, EventArgs e)
        {
            PictureBox pictureBox = new PictureBox();
            pictureBox.Location = cp;
            pictureBox.BackColor = Color.White;
            pictureBox.Size = new Size(50, 50);
            pictureBox.Image = Image.FromFile("testPicture.bmp");
            pictureBox.Parent = panel;

            contextMenuStripOnAdditionalControlMenuItems();     // 追加したコントロールのコンテキストメニューのアイテムを作る

            pictureBox.ContextMenuStrip = contextMenuStripOnAdditionalControl;
        }



        // テキストボックスを作るイベントハンドラー 
        private void PanelMenuItems1_Click(object sender, EventArgs e)
        {
            TextBox textBox = new TextBox();
            textBox.Location = cp;
            textBox.BorderStyle = BorderStyle.FixedSingle;
            textBox.Text = "これはテキストボックスです。";
            textBox.Multiline=true;
            textBox.Size = new Size(100, 100);
            textBox.BackColor = Color.Yellow;
            textBox.Parent = panel;

            contextMenuStripOnAdditionalControlMenuItems();     // 追加したコントロールのコンテキストメニューのアイテム
            textBox.ContextMenuStrip = contextMenuStripOnAdditionalControl;
        }

        //
        // ラベルを作るイベントハンドラー
        private void PanelMenuItems2_Click(object sender, EventArgs e)
        {
            Label label = new Label();
            label.Location = cp;
            label.BorderStyle = BorderStyle.FixedSingle;
            label.BackColor = Color.Pink;
            label.Text = "これはラベルです。";
            label.Parent = panel;

            contextMenuStripOnAdditionalControlMenuItems();     // 追加したコントロールのコンテキストメニューのアイテム

            label.ContextMenuStrip = contextMenuStripOnAdditionalControl;

        }

        // 追加したコントロールのコンテキストメニューのアイテム  (メニューアイテムを分けたいときは、これをさんこうにして別のメソッドを作るといい)
        private void contextMenuStripOnAdditionalControlMenuItems()
        {
            contextMenuStripOnAdditionalControl.Items.Clear();  // 前回作ったアイテムを削除して作り直す。
            contextMenuStripOnAdditionalControl.Items.Add("削除");
            contextMenuStripOnAdditionalControl.Items.Add("追加コンソールのメニューアイテム2");
            contextMenuStripOnAdditionalControl.Items.Add("追加コンソールのメニューアイテム3");

            contextMenuStripOnAdditionalControl.Items[0].Click += AdditionalControlItems0_Click;
            contextMenuStripOnAdditionalControl.Items[1].Click += AdditionalControlItems1_Click;
            contextMenuStripOnAdditionalControl.Items[2].Click += AdditionalControlItems2_Click;
        }


        // 追加したコンソールのメニューアイテム0番(削除)を選択した時のイベントハンドラ
        private void AdditionalControlItems0_Click(object sender, EventArgs e)
        {
            if (contextMenuStripOnAdditionalControl.SourceControl == null) return;

            Control c = contextMenuStripOnAdditionalControl.SourceControl as Control;

            Console.WriteLine("SourceControl={0}", contextMenuStripOnAdditionalControl.SourceControl);
            //Console.WriteLine("Source:" + c.GetType().ToString());
            Console.WriteLine();

            if (c != null)
            {
                panel.Controls.Remove(c);  // フォームに登録されたコントロールのオブジェクトを消す
            }
            else MessageBox.Show("これは削除できません!");
        }


        // 追加したコンソールのメニューアイテム1番を選択した時のイベントハンドラ
        private void AdditionalControlItems1_Click(object sender, EventArgs e)
        {
            Console.WriteLine("items1_Click");
            Console.WriteLine();
        }


        // 追加したコンソールのメニューアイテム2番を選択した時のイベントハンドラ
        private void AdditionalControlItems2_Click(object sender, EventArgs e)
        {
            Console.WriteLine("items2_Click");
            Console.WriteLine();
        }


    }
}


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

Panelなどコントロール(Control)上で右クリックをしてコンテキストメニューを開いた位置に新たにPictureBoxなどのコントロールを追加する

 下の写真のように、水色のPanelの上で、コンテキストメニューを開き、その選択に応じて、新たにPictureBoxなどのコントロールを追加する。

 ポイントは、コンテキストメニューを開くために、右クリックした位置を取得する点である。この点については、dobon.netを参考にした。
https://dobon.net/vb/dotnet/control/cmclickpoint.html

動作画像.png

// 1.コントロール(panel)上で左クリックしたときにコンテキストメニューを開き、コンテキストメニューを開いた時にあったマウスの先端に動的に新たにコントロールを作る。

// 参考資料
// https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.contextmenustrip?view=netframework-4.8

// https://dobon.net/vb/dotnet/control/cmclickpoint.html

using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;

namespace TestSourceControl
{
    class Form3 : Form
    {
        public static void Main()
        {
            Application.Run(new Form3());
        }

        private Panel panel;

        // panelを左クリックしたときのContextMenuStripコントロールの宣言
        private ContextMenuStrip contextMenuStripOnPanel;

        public Form3()
        {
            this.Size = new Size(800, 800);

            // panelのコンテキストメニューを宣言とコンテキストメニューを開いた時のイベントハンドラーの定義
            contextMenuStripOnPanel = new ContextMenuStrip();

            // panelの上で、右クリックをした時のコンテキストメニューを開くためのデリゲードへの登録
            contextMenuStripOnPanel.Opening += new CancelEventHandler(cms_Opening); // 必ずForm3()で宣言する

            panel = new Panel();
            panel.Location = new Point(100, 100);
            panel.Size = new Size(600, 600);
            panel.BorderStyle = BorderStyle.FixedSingle;
            panel.BackColor = Color.SkyBlue;
            panel.Parent = this;
            panel.ContextMenuStrip = contextMenuStripOnPanel;

            Label labelOnForm = new Label();
            labelOnForm.Text = " 水色のパネルの上で、右クリックをして、" + "\r\n" + "コンテキストメニューからコントロールを選択すると、" + "\r\n" + "パネル上の右クリックした際のマウスポインタの先の位置に、選択したコントロールを追加できます。" ;
            labelOnForm.AutoSize![動作画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501068/536124cf-c683-c283-d14f-933326bd830f.png)
 = true; ;
            labelOnForm.Location = new Point(0, 0);
            labelOnForm.Parent = this;

        }

        private Point cp;

        // panel上で右クリックをした時の処理
        void cms_Opening(object sender, CancelEventArgs e)
        {
            ContextMenuStrip menuOnPanel = (ContextMenuStrip)sender;

            // マウスカーソルの位置を画面座標で取得
            Point mp = MousePosition;
            cp = menuOnPanel.SourceControl.PointToClient(mp);
            Console.WriteLine(cp);

            // コンテキストメニューの表示
            contextMenuStripOnPanel.Items.Clear();
            contextMenuStripOnPanel.Items.Add("ピクチャーボックスを追加");
            contextMenuStripOnPanel.Items.Add("テキストボックスを追加");
            contextMenuStripOnPanel.Items.Add("ラベルを追加");

            contextMenuStripOnPanel.Items[0].Click += PanelMenuItems0_Click;
            contextMenuStripOnPanel.Items[1].Click += PanelMenuItems1_Click;
            contextMenuStripOnPanel.Items[2].Click += PanelMenuItems2_Click;

            // キャンセルをfalseに設定します。(イベントをキャンセルするかどうかの設定)
            // 空のエントリに基づいてtrueに最適化されます。
            e.Cancel = false;   //trueにするとコンテキスメニューが表示されない
        }

        //
        // PictureBoxをつくるイベントハンドラ
        private void PanelMenuItems0_Click(object sender, EventArgs e)
        {
            PictureBox pictureBox = new PictureBox();
            pictureBox.Location = cp;
            pictureBox.BackColor = Color.White;
            pictureBox.Size = new Size(50, 50);
            pictureBox.Image = Image.FromFile("testPicture.bmp");
            pictureBox.Parent = panel;
        }

        //
        // テキストボックスを作るイベントハンドラー 
        private void PanelMenuItems1_Click(object sender, EventArgs e)
        {
            TextBox textBox = new TextBox();
            textBox.Location = cp;
            textBox.BorderStyle = BorderStyle.FixedSingle;
            textBox.Text = "これはテキストボックスです。";
            textBox.Multiline=true;
            textBox.Size = new Size(100, 100);
            textBox.BackColor = Color.Yellow;
            textBox.Parent = panel;
        }

        //
        // ラベルを作るイベントハンドラー
        private void PanelMenuItems2_Click(object sender, EventArgs e)
        {
            Label label = new Label();
            label.Location = cp;
            label.BorderStyle = BorderStyle.FixedSingle;
            label.BackColor = Color.Pink;
            label.Text = "これはラベルです。";
            label.Parent = panel;
        }
    }
}


testPicture.png

この画像ファイルは、debugフォルダまたはbinフォルダに置いてください。

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

Panelなどコントロール(Control)上で右クリックをしてコンテキストメニューを開いた位置に新たにPictureBoxなどのコントロールを追加する(その1)

 下の写真のように、水色のPanelの上で、コンテキストメニューを開き、その選択に応じて、新たにPictureBoxなどのコントロールを追加する。

 ポイントは、コンテキストメニューを開くために、右クリックした位置を取得する点である。この点については、dobon.netを参考にした。
https://dobon.net/vb/dotnet/control/cmclickpoint.html

 なお、追加したコントロールを削除したい場合は、(その2:追加したコントロールを削除する)を参照してください。

動作画像.png

// 1.コントロール(panel)上で左クリックしたときにコンテキストメニューを開き、コンテキストメニューを開いた時にあったマウスの先端に動的に新たにコントロールを作る。

// 参考資料
// https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.contextmenustrip?view=netframework-4.8

// https://dobon.net/vb/dotnet/control/cmclickpoint.html

using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;

namespace TestSourceControl
{
    class Form3 : Form
    {
        public static void Main()
        {
            Application.Run(new Form3());
        }

        private Panel panel;

        // panelを左クリックしたときのContextMenuStripコントロールの宣言
        private ContextMenuStrip contextMenuStripOnPanel;

        public Form3()
        {
            this.Size = new Size(800, 800);

            // panelのコンテキストメニューを宣言とコンテキストメニューを開いた時のイベントハンドラーの定義
            contextMenuStripOnPanel = new ContextMenuStrip();

            // panelの上で、右クリックをした時のコンテキストメニューを開くためのデリゲードへの登録
            contextMenuStripOnPanel.Opening += new CancelEventHandler(cms_Opening); // 必ずForm3()で宣言する

            panel = new Panel();
            panel.Location = new Point(100, 100);
            panel.Size = new Size(600, 600);
            panel.BorderStyle = BorderStyle.FixedSingle;
            panel.BackColor = Color.SkyBlue;
            panel.Parent = this;
            panel.ContextMenuStrip = contextMenuStripOnPanel;

            Label labelOnForm = new Label();
            labelOnForm.Text = " 水色のパネルの上で、右クリックをして、" + "\r\n" + "コンテキストメニューからコントロールを選択すると、" + "\r\n" + "パネル上の右クリックした際のマウスポインタの先の位置に、選択したコントロールを追加できます。" ;
            labelOnForm.AutoSize = true; ;
            labelOnForm.Location = new Point(0, 0);
            labelOnForm.Parent = this;

        }

        private Point cp;

        // panel上で右クリックをした時の処理
        void cms_Opening(object sender, CancelEventArgs e)
        {
            ContextMenuStrip menuOnPanel = (ContextMenuStrip)sender;

            // マウスカーソルの位置を画面座標で取得
            Point mp = MousePosition;
            cp = menuOnPanel.SourceControl.PointToClient(mp);
            Console.WriteLine(cp);

            // コンテキストメニューの表示
            contextMenuStripOnPanel.Items.Clear();
            contextMenuStripOnPanel.Items.Add("ピクチャーボックスを追加");
            contextMenuStripOnPanel.Items.Add("テキストボックスを追加");
            contextMenuStripOnPanel.Items.Add("ラベルを追加");

            contextMenuStripOnPanel.Items[0].Click += PanelMenuItems0_Click;
            contextMenuStripOnPanel.Items[1].Click += PanelMenuItems1_Click;
            contextMenuStripOnPanel.Items[2].Click += PanelMenuItems2_Click;

            // キャンセルをfalseに設定します。(イベントをキャンセルするかどうかの設定)
            // 空のエントリに基づいてtrueに最適化されます。
            e.Cancel = false;   //trueにするとコンテキスメニューが表示されない
        }

        //
        // PictureBoxをつくるイベントハンドラ
        private void PanelMenuItems0_Click(object sender, EventArgs e)
        {
            PictureBox pictureBox = new PictureBox();
            pictureBox.Location = cp;
            pictureBox.BackColor = Color.White;
            pictureBox.Size = new Size(50, 50);
            pictureBox.Image = Image.FromFile("testPicture.bmp");
            pictureBox.Parent = panel;
        }

        //
        // テキストボックスを作るイベントハンドラー 
        private void PanelMenuItems1_Click(object sender, EventArgs e)
        {
            TextBox textBox = new TextBox();
            textBox.Location = cp;
            textBox.BorderStyle = BorderStyle.FixedSingle;
            textBox.Text = "これはテキストボックスです。";
            textBox.Multiline=true;
            textBox.Size = new Size(100, 100);
            textBox.BackColor = Color.Yellow;
            textBox.Parent = panel;
        }

        //
        // ラベルを作るイベントハンドラー
        private void PanelMenuItems2_Click(object sender, EventArgs e)
        {
            Label label = new Label();
            label.Location = cp;
            label.BorderStyle = BorderStyle.FixedSingle;
            label.BackColor = Color.Pink;
            label.Text = "これはラベルです。";
            label.Parent = panel;
        }
    }
}


testPicture.png

この画像ファイルは、debugフォルダまたはbinフォルダに置いてください。

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

TextAssetに対応してない拡張子のファイルをInspectorで設定する

TextAssetは拡張子が特定のものじゃないと許容してくれない

テキストアセット ドキュメント

上記によるとTextAssetは

  • .txt
  • .html
  • .htm
  • .xml
  • .bytes
  • .json
  • .csv
  • .yaml
  • .fnt

をサポートしているそうで

今回xLuaというUnityでluaを扱うライブラリのサンプルをみているとluaのファイルの拡張子を.txtにしていた。テキストエディターで開いた際に設定をしなければハイライトなどが適応されないので.luaのままで設定できた方が楽じゃね?と思ったので作ってみました

LuaTextAsset.cs

そこで今回作ったのが.luaを許容するEditor拡張で上のgistで公開しています

xLuaのExample/02_U3DScriptingを変えて

  • LuaBehaviour.csのLuaScript変数の型をTextAsset → LuaTextAsset
  • LuaTestScript.lua.txt → LuaTestScript.lua

に変更させていますが正しく動いているのがわかります(実行が遅いのは低スペックmacなのでご愛嬌)
output.gif

中身

LuaTextAsset.cs
LuaTextAsset.cs
using System;
using System.IO;
using System.Text;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif

[Serializable]
public class LuaTextAsset
{

    public const string Extension = ".lua";

    [SerializeField] private string path;

    [SerializeField] private string textString;

    [SerializeField] private string byteString;

    public string text => textString;

    public byte[] bytes => Encoding.ASCII.GetBytes(byteString);

    public static implicit operator TextAsset(LuaTextAsset textAsset)
    {
        return new TextAsset(textAsset.textString);
    }

    public static implicit operator LuaTextAsset(TextAsset textAsset)
    {
        return new LuaTextAsset { textString = textAsset.text };
    }
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(LuaTextAsset))]
public class LuaInspectorEditor : PropertyDrawer
{

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var path = property.FindPropertyRelative("path").stringValue;
        var loaded = AssetDatabase.LoadAssetAtPath(path, typeof(Object));
        var field = EditorGUI.ObjectField(position, label, loaded, typeof(Object), false);
        var loadPath = AssetDatabase.GetAssetPath(field);
        var fileExtension = Path.GetExtension(loadPath);
        if (field == null || fileExtension != LuaTextAsset.Extension)
        {
            property.Set("path", "");
            property.Set("textString", "");
            property.Set("byteString", "");
        }
        else
        {
            var pathProperty = property.FindPropertyRelative("path");
            property.Set("path", loadPath.Substring(loadPath.IndexOf("Assets", StringComparison.Ordinal)));
            property.Set("textString", File.ReadAllText(pathProperty.stringValue));
            property.Set("byteString", Encoding.ASCII.GetString(File.ReadAllBytes(pathProperty.stringValue)));
        }
    }
}

public static class SerializedPropertyExtension
{
    public static void Set(this SerializedProperty property, string name, string value)
    {
        var pathProperty = property.FindPropertyRelative(name);
        pathProperty.stringValue = value;
    }
}

#endif

基本的にTextAssetと互換を保ちつつ、TextAssetと同じ変数名で扱えるように設定をしています。今回はluaに限定していますが少し変えれば好きな拡張子で設定が可能になると思います。

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

WPFのRichTextBoxにBindingする

概要

WPFで単純な文字列ではなく、複数の書式(文字色, Font, etc.)が入り混じった文字表示をしたい場合はRichTextBoxを使用します。
しかし、このRichTextBoxの中身Documentプロパティは依存関係プロパティではないので、そのままではBindingできず、ViewModelから変更できません。

そこでこれを解決するためのいくつかの方法を紹介します。

解決方法

デモアプリ

解決方法の具体例として、以下のようなデモアプリを考えます。
1つのRichTextBox内に、文字色と内容が固定の文字列("FixText_")と、ViewModelから文字色と内容が変更される文字列があるとします。

RichTextBoxBinding.gif

このデモアプリでは、各行ごとに以降の方法を使って、ViewModelからRichTextBoxの中身を書き換えています。

方法1 Run内のプロパティ単位でBindingする

そもそもRichTextBoxの中身全体をBindingするのではなくて、その一部だけをBindingしてしまう、という方法です。
Runのプロパティは依存関係プロパティなので、問題なくBindingできます。
表題とはずれますが、ViewModelから変更したい内容が、一部分の文字列だけとかであれば、これで十分でしょう。

MainWindow.xaml(部分)
<RichTextBox>
   <FlowDocument>
      <Paragraph>
         <Run  Text="FixText_" />
         <Run
            FontStyle="Italic"
            Foreground="{Binding TextColor}"
            Text="{Binding NormalText}" />
      </Paragraph>
   </FlowDocument>
</RichTextBox>
MainWindowViewModel.cs(部分)
private string _NormalText = "NormalText in VM";
public string NormalText
{
    get => _NormalText;
    set
    {
        _NormalText = value;
        RaisePropertyChanged();
    }
}

方法2 添付プロパティでFlowDocumentまるごとBindingする

もっと柔軟に、RichTextBoxの中身のFlowDocumentをまるごと変更したいということであれば、この方法を使用します。
ただしViewModelにゴリゴリのView情報(RichTextBox)が入っているのでMVVM的にはイマイチです。

BindingできないDocumentプロパティにBindingするための添付プロパティを用意します。
この添付プロパティにFlowDocumentがBindingで渡されると、添付対象のRichTextBoxの本来のDocumentプロパティに対して、そのFlowDocumentが設定されます。
その際にFlowDocumentが複数のRichTextBoxに所属するとエラーしてしまうので、必要であればコピーを作成して設定します。

public class RichTextBoxHelper : DependencyObject
{
    public static FlowDocument GetDocument(DependencyObject obj) => (FlowDocument)obj.GetValue(DocumentProperty);
    public static void SetDocument(DependencyObject obj, FlowDocument value) => obj.SetValue(DocumentProperty, value);

    public static readonly DependencyProperty DocumentProperty = DependencyProperty.RegisterAttached(
        "Document", typeof(FlowDocument), typeof(RichTextBoxHelper),
        new FrameworkPropertyMetadata(null, Document_Changed));

    private static void Document_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is RichTextBox richTextBox))
            return;

        var attachedDocument = GetDocument(richTextBox);

        //FlowDocumentは1つのRichTextBoxにしか設定できない。
        //すでに他のRichTextBoxに所属しているなら、コピーを作成・設定する
        richTextBox.Document = attachedDocument.Parent == null
            ? attachedDocument
            : CopyFlowDocument(attachedDocument);
    }

    private static FlowDocument CopyFlowDocument(FlowDocument sourceDoc)
    {
        //もとのFlowDocumentをMemoryStream上に一度Serializeする
        var sourceRange = new TextRange(sourceDoc.ContentStart, sourceDoc.ContentEnd);
        using var stream = new MemoryStream();
        XamlWriter.Save(sourceRange, stream);
        sourceRange.Save(stream, DataFormats.XamlPackage);

        //新しくFlowDocumentを作成
        var copyDoc = new FlowDocument();
        var copyRange = new TextRange(copyDoc.ContentStart, copyDoc.ContentEnd);
        //MemoryStreamからDesirializeして書き込む
        copyRange.Load(stream, DataFormats.XamlPackage);

        return copyDoc;
    }
}

この添付プロパティをRichTextBoxに対して使用します。

MainWindow.xaml(部分)
<RichTextBox local:RichTextBoxHelper.Document="{Binding Document}" />

ViewModelではコードでFlowDocumentを組み立てます。

MainWindowViewModel.cs(部分)
private FlowDocument _Document = CreateFlowDoc("FlowDocument in VM");
public FlowDocument Document
{
    get => _Document;
    set
    {
        _Document = value;
        RaisePropertyChanged();
    }
}

private static FlowDocument CreateFlowDoc(string innerText)
{
    var paragraph = new Paragraph();
    paragraph.Inlines.Add(new Run("FixText_"));
    paragraph.Inlines.Add(new Run(innerText) { Foreground = new SolidColorBrush(Colors.BlueViolet) });
    return new FlowDocument(paragraph);
}

方法3 添付プロパティとConverterで柔軟にBindingする

柔軟に変更したいが、ViewModelにViewの情報を入れたくない・適度に抽象化したい、という場合はこの方法です。

まず、RichTextBoxのDocumentに対応するViewModelクラスを作成します。
ここでは色と文字列だけ変更するとします。

public class RichTextViewModel
{
    public string Text { get; set; }
    public Color Color { get; set; }
}

それをMainWindowViewModelではプロパティとして公開します。

MainWindowViewModel.cs(部分)
private RichTextViewModel _RichVM = new RichTextViewModel() { Text = "Original Text in VM", Color = Colors.Indigo };
public RichTextViewModel RichVM
{
    get => _RichVM;
    set
    {
        _RichVM = value;
        RaisePropertyChanged();
    }
}

このプロパティをそのままBindingすることはできませんので、ViewModel→FlowDocumentへの変換のためのConverterを作成します。

public class RichTextVmToFlowDocumentConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (!(value is RichTextViewModel richVM))
            return Binding.DoNothing;

        var paragraph = new Paragraph();
        paragraph.Inlines.Add(new Run("FixText_"));
        paragraph.Inlines.Add(new Run()
        {
            Text = richVM.Text,
            Foreground = new SolidColorBrush(richVM.Color),
            FontStyle = FontStyles.Italic,
        });
        return new FlowDocument(paragraph);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

このConverterと方法2の添付プロパティを使用して、ViewModelのプロパティとBindingします。

MainWindow.xaml(部分)
<RichTextBox local:RichTextBoxHelper.Document="{Binding RichVM, Converter={StaticResource RichTextVmToFlowDocumentConverter}}" />

デモアプリコード全体

デモアプリのコード全体はGithubにおいておきます。

https://github.com/soi013/RichTextBoxBinding

注意点

Viewからの変更をViewModelで取得するのは難しいです。基本的にOneWayのみ。
方法2で、内部でFlowDocumentがコピーされていなければViewModel側にも反映されていますが、Documentプロパティ自体は変更されていないので、変更通知などは発生しません。

参考

RichTextBox とデータバインディングする

https://stackoverflow.com/questions/343468/richtextbox-wpf-binding

https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.richtextbox?view=netframework-4.8

環境

VisualStudio2019
.NET Core 3.1
C#8

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

【Unity初心者】関数(メソッド)って何? 

環境

Unity 2019.3.7f1

はじめに

メソッドは日本語でいうと関数
なかなか馴染みがない言葉ですよね。
今回は概略を説明していきます。

関数(メソッド)とは?

関数とは処理をまとめたものです。
ざっくり図にするとこんな感じです。
image.png

そして、大きくわけると次の2つに分かれます。
・C#やUnity側であらかじめ処理内容が用意されている関数
・自分で処理内容を決める関数
さらに返り値の有無引数の有無で細分化されていきます。
下図が関数の分類と使い方になります。(イベント関数は使い方が特殊なので別で書きました。)
image.png

全11種類です!(多いわ!)
1.用意されている関数-返り値有-引数必要
2.用意されている関数-返り値有-引数不要
3.用意されている関数-返り値無-イベント関数-引数必要
4.用意されている関数-返り値無-イベント関数-引数不要
5.用意されている関数-返り値無-引数必要
6.用意されている関数-返り値無-引数無-①
7.用意されている関数-返り値無-引数無-②
8.自作の関数-返り値有-引数有
9.自作の関数-返り値有-引数無
10.自作の関数-返り値無-引数有
11.自作の関数-返り値無-引数無

関数を使うと具体的に何ができるの?

大分類2種類でそれぞれ説明

C#やUnity側であらかじめ処理内容が用意されている関数
スクリーンサイズを取得したり、変数の値をコンソールに表示したり様々なことができます。

関数の種類については非常にたくさんありますので、
詳細はkimamaさんのサイトを参考にして下さい。↓
http://kimama-up.net/unity-functions-application/

自分で処理内容を決める関数
if文や四則演算やfor文などの自分のやりたい処理をひとまとめにすることができます。
・何回も同じコードを書かなくて良い
・コードが見やすくなる
といったメリットがあります。

使い方をちゃんと説明してよ

全11種類の関数の使い方、自作関数の作り方については今後記事作成していきます。

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

メモ:C#でregeditからコピーしたバイナリを受け取る

regeditからバイナリをコピーしてきてC#でデコードしたりするときに、クリップボードを経由できるとちょっと便利。

バイナリの編集画面からコピーすると"RegEdit_HexData"形式でクリップボードに入る。IDataObject経由で取り出して、GetFormatsでこのフォーマットが含まれているか確認して、GetDataで取り出す。取り出したデータはMemoryStreamなので、キャストしてバッファにコピーする。
今回は全体を取り出したが、バッファの先頭4バイトに32bitLEで長さが入っているらしいので、最初に4バイト取り出して長さを得る、もしくは最初の4バイトを読み捨てる、といった処理をやっても良さそう。

const string format = "RegEdit_HexData";

IDataObject clipboardObj = Clipboard.GetDataObject();

if (!clipboardObj.GetFormats().Contains(format))
{ throw new Exception("ひたいおうのけいしき!!"); }

byte[] buff;

using (MemoryStream ms = (MemoryStream)clipboardObj.GetData(format))
{
    buff = new byte[ms.Length];
    ms.Read(buff, 0, buff.Length);
}

int length = BitConverter.ToInt32(buff, 0);

そもそもMicrosoft.Win32.Registry使えって話なんだが(結局それ経由で取り出すようにしたのでClipboard経由は不要になったんだが、せっかく方法を調べたので捨てるにしのびなくメモ)。

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

InputSystemでPress Any Keyを作る

概要

PC向けのゲームを真面目に作ろうとすると、キーボード、ゲームパッド、マウス他の対応。
特に全部に対応したタイトル画面のPress Any Keyを作るのが面倒くさいですよね。
各デバイスごとの決定キーだけ対応させるっていう手抜きもありえますが、新しいInputSystemなら取得に必要なコードは実質1行!
おそらくゲームパッド上のメニューボタンなどにも反応してしまうので使う場合はお気をつけて

環境

2019.3.6f1

ソース

    public class TitleManager : MonoBehaviour
    {
        private InputAction _pressAnyKeyAction = 
                new InputAction(type: InputActionType.PassThrough, binding: "*/<Button>", interactions: "Press");

        private void OnEnable() => _pressAnyKeyAction.Enable();
        private void OnDisable() => _pressAnyKeyAction.Disable();

        void Update()
        {
            if(_pressAnyKeyAction.triggered)
            {
                //シーン読み込み、アニメーション読み込みなどなど
            }
        }
    }

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