ごまなつ Blog

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

【C#】filestreamは既存ファイルに上書きできないのか?

zipファイルを読み込み、中身のxmlファイルの内容を編集してzipファイルを保存するソフトを作っています。その中で、「多重起動できて、同じファイルを開いたときは2つ目以降は編集できないように、異なるファイルは編集できるように」という要望があったため、filestreamを用いて開いているファイルの判断を行い、filestreamで保存するようにしました。

削除が保存できていない?

新しいxmlファイルを作る処理があり、その処理をした後一旦保存。その後削除してファイルを確認すると、削除したはずのファイルが削除されていません。

パスワードが変更される?

今回のzipファイルにはパスワードをかけていたのですが、そのパスワードが変更されていました。

奇怪な動作の理由

filestreamは、上書きをする処理がStreamWriterとは異なるようです。filestreamとして確保した長さより短いデータだと、既存のデータが残るようです。よって、StreamWriterを用いたいのですが、ファイルロックを使いたいのであればfilestreamを用いる必要があります。

解決策

fileStream.SetLength(0);

これを使うと正常にできるようになりました。filestreamの長さを強制的に0に設定することによって、常にfilestreamより長いデータである状況にしています。ほかの良い方法があるかもしれないので、調べていきます。

【C#】DataGridViewで、チェックボックスの列を一括で変更したい

DataGridViewでチェックボックスの列を一括で変更したいときに苦しんだメモです。

DataGridViewの値を取得したいとき

DataGridViewには、3つのValueが存在します。

  • Value
  • FormattedValue
  • EdittedFormattedValue

Datagridviewは、実は表示用のDataGridViewが上にあり、その下に実際のDataGridViewが存在しています。つまり、自分で値を変更しても表示用のDataGridViewしか値が変わっていません。 上記のValueは、実際のDataGridViewの値を参照するため、Valueだと変更されていません。 FormattedValueが表示用のDataGridViewの値を参照するため、変更を感知するときはFormattedValueになります。 ちなみに、EdittedFormattedValueは、現在の書式指定済みの値を取得します。FormattedValueとの違いがまだ分かっていません。

DataGridViewのチェックボックスのColumn

DataGridViewは、Columnに型を設定できます。Columnにboolを設定すると、チェックボックスのColumnになります。上記の仕様の通り、変更すると表示用の値しか変わりません。他のセルを選択することによってはじめて、Valueが変更されます。これを解決するために、CurrentCellDirtyStateChanged()を使いました。

private void DataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
        {
            var dgv = (DataGridView)sender;
            if (dgv.IsCurrentCellDirty)
            {
                dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
            }
        }

これで、チェックを入れた瞬間にValueも変更されます。

一括でチェックを入れたい

DataGridViewのセルを範囲選択して、チェックを入れられるようにしようと考えました。 DataGridViewのチェックボックスに設定したColumnに右クリックメニューを追加し、一括チェック操作を書きました。今回チェックを入れるColumnは、indexは1です。

 foreach (DataGridViewCell a in dgv.SelectedCells)
                        {
                            if (a.ColumnIndex == 1)
                            {
                                a.Value = true;                               
                            }
                        }

セルの選択は複数Columnにわたることもできるので、回避のためColumnIndexを指定しています。表示ではすべてチェックが入ったのですが、実はValueが変更されていない場所が1か所ありました。*右クリックしたセルは、Valueが変更されていなかったのです。もっと調べると、一つずつ変更した場合はCurrentCellDirtyStateChangedが発火していますが、一括変更の場合は発火していませんでした。

解決法

チェックボックスの値を表示用が変更された瞬間にValueを変更するためには、EndEdit()を使います。これをすることで今回の問題は解決しました。

まとめ

DataGridViewのvalueの仕様から起こる挙動をチェックボックスの一括値変更から解説しました。DataGridViewの仕様を学んだので、次に生かしていこうと思います。

【C#】ListViewのSelectedIndex変更禁止に立ちふさがる壁

C#のListViewにて、ある条件のときは選択しているアイテムを変更させないようにしたいことがありました。アイテム変更なのでitemSelectionChangedを用いました。選択しているアイテムの変更は、

  1. 選択しているアイテムの選択が外れる
  2. 何も選択されていない状態
  3. クリックしたアイテムが選択される

という順番になっています。

避けるべきこと

上記の2においては、何も選択されていない状態なのでインデックスが-1になります。また、3において選択操作をキャンセルすると何も選択していない状態になります。これを回避する必要があります。

使えるもの

ListViewItem型の変数を用意して、itemSelectionChangedの中で使うと選択しているアイテムになります。e.itemは変更先のアイテムです。また、MouseUpイベントハンドラ(マウスクリックを離したとき)を用いて選択しているアイテムにインデックスを戻せば、アイテムを変更させないことができます。

問題

この考えに従って普通に書くと、itemSelectionChangedの中で選択しているアイテムを変更することになるので、処理の順番などから思わぬ動作になることがあります。私も、選択が消えることが多くありました。よって、Task.Runを用いてアイテム変更動作のタイミングをずらしました。

追加要望

ここで、右クリックメニューを追加してほしいという要望が来ました。上記の動作を持ったまま行うと、選択しているアイテムに選択を戻すため、選択しているアイテムが選択状態になる(背景青に白抜きの文字になる)が、右クリックメニューはクリック位置に出るという違和感満載の見た目になりました。なにか良い方法があるとよいのですが・・・・・・

【C#】UnixTimeの現在時刻変換でやらかした2点

UnixTimeから現在時刻に変換して表示をしていたのですが、2点やらかしたので、そのまとめです。

変換には以下を用いました。

 private readonly static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static long ToUnixTime(DateTime dateTime)
        {
            dateTime = dateTime.ToUniversalTime();
            TimeSpan elapsedTime = dateTime - UnixEpoch;
            return (long)elapsedTime.TotalSeconds;
        }
        public static string FromUnixTime(long unixTime)
        {
            return UnixEpoch.AddSeconds(unixTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm");
        }

ToLocalTime()の書き忘れ

ToLocalTime()は現地時間への変換です。日本ではUTCと9時間ずれているため、変換しないと9時間ずれます。

カスタム日時形式文字列

docs.microsoft.com

DateTime.Now.ToString(””yyyy/MM/dd HH:mm:ss")のようなものです。よく使うのは、以下だと思います。

  • yyyy:4けたの年
  • yy:0埋め2けたの年
  • MM:0埋め2けたの月
  • dd:0埋め2けたの日
  • ddd:曜日の省略名
  • dddd:曜日の完全名
  • HH:0埋め2けたの時間(24時間表記)
  • hh:0埋め2けたの時間(12時間表記)
  • mm:0埋め2けたの分
  • ss:0埋め2けたの秒

現地時間に変換すると、どうしても12時間ずれました。なぜか。HHをhhと書いていました。24時間表記を期待しているのに12時間表記のため、午後に操作すると12時間ずれていました。また、日にちの部分にmmと書いてしまいました。

まとめ

UnixTimeは間違っていないのに、DateTimeに変換して表示するとなにかおかしい。そんなときには、現地時間への変換が行われているか、カスタム日時形式文字列は間違っていないか確認しようと思います。

「これからの生き方。」を読んだ

あなたの価値観はどのようなものですか?

これがこの本の主題だと感じました。生き方を自分の価値観に従ったものにすることで幸せに近づける、自分の価値観を知って生き方を考えようと語りかけられている気がしました。

概要

最初は漫画で、仕事への価値観が異なるキャラクター達の物語が展開されます。そのあと、価値観を知るワークがあり、著者自身の経験から感性を磨くことの大切さが書いてあります。

仕事への価値観

  1. 能力の活用(自分の能力を発揮できること)
  2. 達成(良い結果が生まれたという実感)
  3. 美的追求(美しいものを創り出せること)
  4. 愛他性(人の役に立てること)
  5. 自立性・自律性(自律できること)
  6. 創造性(新しいものや考え方を作り出せること)
  7. 経済的価値(たくさんのお金を稼ぎ、高水準の生活を送れること)
  8. ライフスタイル(自分の望むペース、生活ができること)
  9. 身体的活動(体を動かす機会が持てること)
  10. 社会的評価(社会に仕事の成果を認めてもらうこと)
  11. 危険性、冒涜性(わくわくするような体験ができること)
  12. 社会的交流(いろんな人と接点を持ちながら仕事をできること)
  13. 多様性(多様な活動ができること)
  14. 環境(仕事環境が心地よいこと)

これらの要素がそれぞれの人によって異なります。会社にはいろいろな人が存在するので、分かり合えないこともあります。それでも、うまくいくよう考えていかないとな、と思いました。

キャリアの型

スキル型、意志型、チーム型、バランス型が紹介されています。何を強みにするかで分類されています。それぞれ強みと弱みが書かれています。 自分は特徴を確認すると、当てはまっている部分と当てはまらない部分がすべてに存在していて、明確にこの型だと言い切れませんでした。 だからこそ、バランス型なのかもしれないですね。

感性を磨く

自分の感性や価値観を知っておかないと、どんなに若くても「人生を持て余す」感覚に陥るそうです。そうなると、生き方を問われる人になってしまいます。そのため、自分の感性を磨いて理解することを推奨しています。自分の感情や言動に対して、なぜこう思ったのか、なぜこうしたのかということを考えていくことで感性が磨かれます。

感想

読み終わった後、これから頑張ろうと思える本でした。勇気をもらえる本だと思います。

【C#】BinaryFormatterを用いた配列のコピーはかなり遅い

結論

配列をディープコピーしたいのであれば、一つずつコピーした方が良いです。

経緯

C#では、クラスの配列をArray.Copyしたときシャローコピーになります。正確に言うと、2階層目からシャローコピーになります。1次元の配列であれば、Array.Copyでディープコピーになりますが、リストのリストといった2階層目があると2階層目からシャローコピーになります。リストのリストをディープコピーしたかったので、このような方法を用いました。

public static T DeepCopyT<T>(T target)
        {
            T result;
            BinaryFormatter b = new BinaryFormatter();
            MemoryStream mem = new MemoryStream();

            try
            {
                b.Serialize(mem, target);
                mem.Position = 0;
                result = (T)b.Deserialize(mem);
            }
            finally
            {
                mem.Dispose();
            }

            return result;
        }

BinaryFormatterでMemoryStreamをシリアライズ・デシリアライズすることで、コピーしたものを返しています。 今回私がこのメソッドに渡していた配列は、class[1000]でした。クラスはa[23].b[20].c[8]個の構造でした。これを上記のメソッドに丸ごとクラスの配列を渡すと、 何と2分かかりました。さすがに実用に耐えないということで、対策を調べるとBinaryFormatterの配列のシリアライズ・デシリアライズは遅いということが分かりました。よって

for(int i=0;i<class.Length;i++)
{
          copyArray[i] = DeepCopy.DeepCopyT(class[i]);
}

として一つずつ渡すようにすると20秒で終わりました。

【C#】DataGridViewComboBoxを一度のクリックで開きたい

結論

F4を押す処理を追加する方法が出てくるが、F4はALTと同時押しするとアプリケーション終了になるなど、ショートカットキーに使われており何か起こる可能性があるのでお勧めしない。 CellEnterイベントで、DropedDownを使おう

private void DataGridView_CellEnter(object sender, DataGridViewCellEventArgs e)
        {
            DataGridView dgv = (DataGridView)sender;
            if(dgv.Columns[e.ColumnIndex].Name == columnName 
            {
                dgv.BeginEdit(false);
                ((DataGridViewComboBoxEditingControl)dgv.EditingControl).DroppedDown = true;
            }
        }

経緯

現在作成しているフォームアプリでは、DataGridViewComboBoxColumnを追加しています。DataGridViewでコンボボックスのカラムを使うと、コンボボックスが開くまでに2回クリックしなければなりません。

  • 1回目のクリックでこのカラムがクリックされたか検知
  • 2回目のクリックでコンボボックスを開く

という処理になっているからです。これは直感に反するので、一回のクリックで開くようにする方法を調べました。すると、CellEnterイベントでSendKeys.Send("{F4}");する方法が見つかりました。これでもできたのですが、ALTを押しながらだとALT+F4となりアプリケーションが落ちるのでは?と考え実際にやってみると落ちました。F4はショートカットキーに使われるため、この方法は良くないと考え、DropedDownを使う方法を採用しました。

感想

処理は実現できたのですが、思わぬところで不具合が出ることがあるのでそこも想定したうえで採用する処理を考えていこうと思いました。