ごまなつ Blog

楽しく働ける世界を目指して

【C#】親フォームの中央に子フォームを表示したい

結論

親フォーム側で子フォームを呼び出すときに、

var frm = new Form1;
frm.Owner = this;
frm.StartPosition = FormStartPosition.CenterParent;

経緯

フォームアプリを作っており、子フォームにデータの内容を入力して、その内容を親フォームに反映するようにしていました。なにも設定せずに呼び出すと、子フォームが親フォームと離れた位置に表示されることがありました。よって、子フォーム側に

this.Location = new Point(
this.Owner.Location.X + (this.Owner.Width - this.Width) / 2,
this.Owner.Location.Y + (this.Owner.Height - this.Height) / 2);

を記載し、子フォームを表示したところ位置は親フォームの中心だったのですが、デュアルディスプレイの場合、サブディスプレイに親フォームを移動してから子フォームを表示すると、メインディスプレイに子フォームが表示されます(計算した座標は合っている)。

どちらのディスプレイに表示されているかを取得し、設定してもよかったのですが、調べていくうちに親フォーム側に設定することで実現できることが分かりました。それが

var frm = new Form1;
frm.Owner = this;
frm.StartPosition = FormStartPosition.CenterParent;

でした。呼び出す前(ShowDialogする前)に、親フォームは自分だと指定して、StartPositionを親の中心にする設定で実現できました。これなら、デュアルディスプレイでも親フォームの中心に子フォームが表示されます。

MessageBox

MessageBoxは、位置を指定するプロパティはなく、ディスプレイの中心に表示されます。位置を指定したいなら、拡張メソッドを書くしかないようです。

【C#】FolderBrowserDialogが使いにくいので、OpenFileDialog でフォルダを指定する

こちらの記事を参考にしています。 qiita.com

実現できるメソッド

void BrowseFolder()
{
    using (var ofd = new OpenFileDialog() { FileName = "SelectFolder", Filter = "Folder|.", CheckFileExists = false })
    {
        if (ofd.ShowDialog() == DialogResult.OK)
        {
            Console.WriteLine(Path.GetDirectoryName(ofd.FileName));
        }
    }
}

(参考記事から引用)

FolderBrowserDialog

  • 選択したフォルダのパスを取得
  • パス直打ちが無い
  • クイックアクセスが無い
  • フォルダを開いたときのツリー構造が見づらい

というところがあります。また、ファイル選択をする場面が多いため、OpenFileDialogのほうがイメージされやすく、その使い勝手を要求されます。よって、OpenFileDialogでフォルダ選択できるのが便利だと思われます。上記のメソッドを使えば、OpenFileDialog でフォルダのパスを取得することができます。

なぜFolderBrowserDialogはOpenFileDialogに変更要求を受けるのか

これはよく使われているのがOpenFileDialogであり、こちらをよく見慣れて・使い慣れているからだと考えます。FolderBrowserDialogにも良いところはあるのに、メジャーになっている方を求められるということではないでしょうか。

【C#】ドラッグ&ドロップが作動しないのは、権限が異なるため

ドラッグ&ドロップでファイルパスを取得する機能がWindowsアプリにあります。その機能を実装したにもかかわらず、VisualStudio上で動かしてもこの機能が動作しない。ドラッグ&ドロップできるときは、マウスカーソルが変化しますがそれすらも起こらない。調べた結果のメモです。

結論

VisualStudioをユーザ権限で開いてください(管理者権限で開かない)

経緯

以前開発していたアプリは、Windowsのサービスに追加されて常駐するものでした。Windowsのサービスに追加されるため、VisualStudioのデバッグ環境上でも毎回「VisualStudioが変更を加えることを許可しますか」のダイアログが出て煩わしかったため、VisualStudioを常に管理者権限で起動するように変更していました。

今回の状況は、権限が異なるアプリで、Windowsによって操作がブロックされています。VisualStudioが管理者権限で起動しているため、VisualStudioでのデバッグ環境で起動しているアプリは管理者権限、しかしフォルダエクスプローラはユーザ権限で開いているため、そこのフォルダからのドラッグ&ドロップは受け付けなかった、ということだと思われます。

【C#】クラスの配列要素の代入は、シャローコピーになる

C#で、xmlファイルをシリアライズ・デシリアライズするためのクラスを宣言して、その内容を変更していました。そこで、配列要素の代入なのにシャローコピーに苦しめられたのでメモです。

public class exampleclass
    {
        [System.Xml.Serialization.XmlElement("version")]
        public string version { get; set; }
        [System.Xml.Serialization.XmlElement("id")]
        public string mapid { get; set; }
        [System.Xml.Serialization.XmlElement("name")  
        public string mapname { get; set; }
        [System.Xml.Serialization.XmlElement("element")]
        public List<band> element { get; set; }
    }

public exampleclass[] Array1 = new Chmap[1000

要素名は適当ですが、このようなクラスがあり、そのクラスの2つの配列を宣言します。この後、

Array1[4] = Array1[0]
Array1[4].version = "1.99"

のように要素を別のインデックスにコピーした後、その要素を変更します。すると、Array1[0].versionも"1.99"に変更されてしまいます。

ディープコピーとシャローコピー

ディープコピー(深いコピー)とシャローコピー(浅いコピー)の違いは、簡単に言うとコピー先の変更がコピー元にも反映されるのがシャローコピー、そうでないのがディープコピーです。シャローコピーは参照をコピーしているだけなので、同じものを参照しているのでどちらも変更された後の配列を参照していることになります。

C#では、配列やリストを=で代入すると、シャローコピーになります。配列やリストは参照型です。int,doubleといった値は値型なのでディープコピーになります。

では配列やリストをどうやってディープコピーするのかですが、配列はArray.Copy,リストはコンストラクタで宣言します。

int[] a = new int[5]
int[] b = new int[5]
Array.Copy(a, b, a.Length)

var list  = new List<int>{1,2}
var list2 = new List<int>(list)

これで良いかと思われたのですが、今回のクラスの場合のような多重構造になると、直下はディープコピーになりますが、それ以降の階層ではシャローコピーになります。

参考にしたページ qiita.com

自分が採用した方法

こちらのページのメソッドを使用しました。

l-s-d.sakura.ne.jp

多重構造でもディープコピーしたい場合は自力で実装するしかないようです。MemoryStreamを用いてシリアライズ・デシリアライズしているようです。

【C#】ウィンドウの大きさに応じてパーツの大きさも変えたい

デフォルトでは、ウィンドウを最大化しても配置されたパーツはその大きさのままで、変更されません。そのため、不自然になります。ウィンドウの大きさに追従して、パーツを大きくするためには、Anchorプロパティを変更します。

Anchorプロパティ

ウィンドウが大きくなった時に、ウィンドウの端との距離を固定するかどうかを設定するプロパティです。要するに、追従させるってことですね。Top, Bottom, Left, Rightの4つを複数個から指定できます。

プロパティウィンドウで指定するときは、白ではなく灰色になっている方向が指定されています。

すべて指定していれば、大きさに追従するようになります。

パーツの上に載っているパーツは、どちらのパーツも指定しないと追従しない

例えば、Tabcontrolで3つのページを作り、それぞれにListViewを載せたとします。この時、ListViewだけにAnchorを指定しても追従しません。TabcontrolのAnchorも指定しましょう。

【C#】配列のコピーは=でやってはいけない

基本的な話です。

配列aを配列bにコピーして、その後配列bの要素を変更したいとします。

a[3]=[1,2,3]
b[3];
a=b;
b[3]= b[3]+2;

普通にコピーするだけならこれでいいだろ、と思うのですが、これでは参照ごとコピーされ、a[3]の値も変更されてしまいます。つまり、a=bとした場合、aが変更されたらbが、bが変更されたらaも変更されてしまいます。よって、どちらも変更したい場合でなければ、Array.Copyを使いましょう。

Array.Copy(コピー元、コピー先、長さ)

この動作を知っているとなにかの役に立つかもしれません。

【C#】ListViewで移動禁止にしたい

結論

private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            if (e.IsSelected && (e.Item != selectedItem))
            {
                if (isChanged)  
                {
                    e.Item.Selected = false;
                    selectedItem.Selected = true;                   
                    if (Counter)
                    {
                        Counter = false;
                        MessageBox.Show("移動できません");
                    }
                    else
                    {
                        Counter = true;
                    }
                }
                else
                {
                    selectedItem = e.Item;
                }
            }
        }
        private void listView1_MouseUp(object sender, MouseEventArgs e)
        {
            selectedItem.Selected = true;   //これが無いと選択状態が消える
        }

動作

イベントハンドラで、選択されているアイテムが変更されるときに、選択されているアイテムがイベントハンドラを発生させていて、アイテムは異なるときに変更があったら選択を無効化して選択されたアイテムに戻しています。 このCounterというのは、選択を変更する一連の動作がItemSelectionChangedを2回呼ぶので、1回しかメッセージボックスを出さないように入れています。

MouseUpを入れておかないと、選択状態が消えます。listViewはほかの場所をクリックすると選択が消えますよね。