20200525のC#に関する記事は15件です。

C# 勉強(3) ファイル入力2

テキストファイルを読み込んで、特定店舗の行のみ出力する。

Program.cs
using System;
using System.IO;
using System.Text;

namespace List094
{
    class Program
    {
        static void Main(string[] args)
        {
        //    var filePath = @"C:\Example\Greeting";
            string filePath = "Sales.txt";
            string[] lines = File.ReadAllLines(filePath,Encoding.UTF8);
            foreach(var line in lines){
                string[] items = line.Split(',');
                Sale sale =new Sale{
                    ShopName = items[0],
                    Product =items[1],
                    Amount =int.Parse(items[2])
                };
                if(sale.ShopName=="浅草店")
                    Console.WriteLine("{0}  {1}  {2}",sale.ShopName,sale.Product,sale.Amount);
            }
        }
    }
}

クラス

Sales.cs
using System;

namespace List094 {
    public class Sale{
        public String ShopName { get; set; }
        public String Product { get; set; }
        public int Amount { get; set; }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# 勉強(4) ファイル入力2

テキストファイルを読み込んで、特定店舗の行のみ出力する。

:Program.cs
using System;
using System.IO;
using System.Text;

namespace List094
{
    class Program
    {
        static void Main(string[] args)
        {
        //    var filePath = @"C:\Example\Greeting";
            string filePath = "Sales.txt";
            string[] lines = File.ReadAllLines(filePath,Encoding.UTF8);
            foreach(var line in lines){
                string[] items = line.Split(',');
                Sale sale =new Sale{
                    ShopName = items[0],
                    Product =items[1],
                    Amount =int.Parse(items[2])
                };
                if(sale.ShopName=="浅草店")
                    Console.WriteLine("{0}  {1}  {2}",sale.ShopName,sale.Product,sale.Amount);
            }
        }
    }
}

クラス

:Sales.cs
using System;

namespace List094 {
    public class Sale{
        public String ShopName { get; set; }
        public String Product { get; set; }
        public int Amount { get; set; }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スマートデバイスプログラミング④(上からランダムに降ってくるボールを拾ってスコアを競うゲーム)

UnityとGameCanvasのダウンロード

1:UnityHubをDLしてインストール。(一部のwindows環境では拡張子を.exeに変更する必要があります)

2:UnityHubを起動して、Unity2019.3.11f1をインストール。

3:GameCanvasのダウンロード
https://github.com/sfc-sdp/GameCanvas-Unity/
でGameCanvasをダウンロード

4:追加画像をダウンロードしてAssetsに追加
http://web.sfc.keio.ac.jp/~wadari/sdp/k04_res.zip

Game.csの編集

Assets内のGame.csを別エディタで編集

using Sequence = System.Collections.IEnumerator;

/// <summary>
/// ゲームクラス。
/// 学生が編集すべきソースコードです。
/// </summary>
public sealed class Game : GameBase
{
  const int BALL_NUM = 30;
  int[] ball_x = new int [BALL_NUM];
  int[] ball_y = new int [BALL_NUM];
  int[] ball_col = new int [BALL_NUM];
  int[] ball_speed = new int [BALL_NUM];
  int ball_w = 24;
  int ball_h = 24;
  int player_x = 304;
  int player_y = 448;
  int player_speed = 3;
  int player_w = 32;
  int player_h = 32;
  int player_img = 4;
  int score = 0;
  int time = 1800;
  int player_col = 4;
  int combo = 0;

    /// <summary>
    /// 初期化処理
    /// </summary>
    public override void InitGame()
    {
      gc.SetResolution(640, 480);
      for(int i =0 ; i < BALL_NUM ; i ++ )
      {
        resetBall(i);
      }
    }

    /// <summary>
    /// 動きなどの更新処理
    /// </summary>
    public override void UpdateGame()
    {
      time = time - 1;

      for(int i =0 ; i < BALL_NUM ; i ++ )
      {
        ball_y[i] = ball_y[i] + ball_speed[i];
        if(ball_y[i]> 480){
          resetBall(i);
        }

        if(gc.CheckHitRect(ball_x[i],ball_y[i],ball_w,ball_h,player_x,player_y,player_w,player_h)){
          if(time>=0){
            score=score+ball_col[i];//ballごとに違った点数を加える
            if(player_col == ball_col[i]) {
              combo++;
              score+= combo;
            }
            else {
              combo = 0;
            }

            player_col = ball_col[i];
          }
          resetBall(i);
        }

      }
      if(gc.GetPointerFrameCount(0) > 0 ){
        if(gc.GetPointerX(0) > 320) {
          player_x += player_speed;
          player_img = 4;
        }
        else {
          player_x -= player_speed;
          player_img = 5;
        }
      }
      if(player_x<=0){
        player_x=0;
      }
      if(player_x>=616){
        player_x=616;
      }


    }

    /// <summary>
    /// 描画の処理
    /// </summary>
    public override void DrawGame()
    {
      gc.ClearScreen();

      for(int i =0 ; i < BALL_NUM ; i ++ ){
        gc.DrawImage(ball_col[i],ball_x[i],ball_y[i]);
      }

      gc.SetColor(0,0,0);
      if(time>=0){
        gc.DrawString("time:"+time,0,0);
      }
      else {
        gc.DrawString("finished!!",0,0);
      }
      gc.DrawString("score:"+score,0,24);
      gc.DrawString("combo:"+combo,0,48);

      //gc.DrawClipImage(player_img,player_x,player_y,0,64,32,32);
      if(time>=0){
        int u = 32+ ((time%60)/30)*32;//0.5秒ごとにユーザー画像を切り替えx
        int v = (player_col - 1) *32;//最初4 色に応じてユーザーの色の画像を指定y
        gc.DrawClipImage(player_img,player_x,player_y, u,v,32,32);
      }
      else {
        gc.DrawClipImage(player_img,player_x,player_y, 96,(player_col - 1) *32,32,32);
      }


    }
    void resetBall(int id){
      ball_x[id] = gc.Random(0,616);
      ball_y[id] = -gc.Random(24,480);
      ball_speed[id] = gc.Random(3,6);
      ball_col[id] = gc.Random(1,3);
    }
}

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

スマートデバイスプログラミング③(制限時間内にボールを反射させてブロックを壊すゲーム)

UnityとGameCanvasのダウンロード

1:UnityHubをDLしてインストール。(一部のwindows環境では拡張子を.exeに変更する必要があります)

2:UnityHubを起動して、Unity2019.3.11f1をインストール。

3:GameCanvasのダウンロード
https://github.com/sfc-sdp/GameCanvas-Unity/
でGameCanvasをダウンロード

Game.csの編集

Assets内のGame.csを別エディタで編集

using Sequence = System.Collections.IEnumerator;

/// <summary>
/// ゲームクラス。
/// 学生が編集すべきソースコードです。
/// </summary>
public sealed class Game : GameBase
{
  int ball_x;
  int ball_y;
  int ball_speed_x;
  int ball_speed_y;
  int player_x;
  int player_y;
  int player_w;
  int player_h;

  const int BLOCK_NUM = 50;
  int[] block_x = new int [BLOCK_NUM];
  int[] block_y = new int [BLOCK_NUM];
  bool[] block_alive_flag = new bool [BLOCK_NUM];
  int block_w = 64;
  int block_h = 20;
  int time ;

    /// <summary>
    /// 初期化処理
    /// </summary>
    public override void InitGame()
    {
      gc.SetResolution(640, 480);
      ball_x = 0;
      ball_y = 0;
      ball_speed_x = 3;
      ball_speed_y = 3;
      player_x = 270;
      player_y = 460;
      player_w = 100;
      player_h = 20;

      for(int i =0 ; i < BLOCK_NUM ; i ++ )
      {
        block_x[i] = (i % 10 ) * block_w;
        block_y[i] = (i / 10 ) * block_h;
        block_alive_flag[i] = true;
      }
      time = 0;
    }

    /// <summary>
    /// 動きなどの更新処理
    /// </summary>
    public override void UpdateGame()
    {
      if(countBlock()!=0){
        time++;
      }
      player_y = gc.GetPointerY(0) - player_h/2;

      ball_x = ball_x + ball_speed_x;
      ball_y = ball_y + ball_speed_y;

      if( ball_x < 0 ) {
        ball_x = 0;
        ball_speed_x = -ball_speed_x;
      }

      if( ball_y < 0 ) {
        ball_y = 0;
        ball_speed_y = -ball_speed_y;
      }

      if( ball_x > 616 ) {
        ball_x = 616;
        ball_speed_x = -ball_speed_x;
      }

      if( ball_y > 456 ) {
        ball_y = 456;
        ball_speed_y = -ball_speed_y;
      }
      if(gc.GetPointerFrameCount(0) > 0 ){
        player_x = gc.GetPointerX(0) - player_w/2;
      }

      if(gc.CheckHitRect(ball_x,ball_y,24,24,player_x,player_y,player_w,player_h)){
        if(ball_speed_y>0){
          ball_speed_y = -ball_speed_y;
        }
      }

      for(int i = 0; i<BLOCK_NUM; i++){
        if(gc.CheckHitRect(ball_x,ball_y,24,24,block_x[i],block_y[i],block_w,block_h)){
          block_alive_flag[i]=false;
        }
      }
    }

    /// <summary>
    /// 描画の処理
    /// </summary>
    public override void DrawGame()
    {
      // 画面を白で塗りつぶします
        gc.ClearScreen();

        // 0番の画像を描画します
        gc.DrawImage(0, 0, 0);

          gc.DrawImage(1,ball_x,ball_y);

        gc.SetColor(0, 0, 255);
        gc.FillRect(player_x,player_y,player_w,player_h);



        for(int i = 0; i<BLOCK_NUM; i++){
          if(block_alive_flag[i]){
            gc.FillRect(block_x[i],block_y[i],block_w,block_h);
          }
        }


        gc.DrawString("time:"+time,60,0);
        if(countBlock()==0){
          gc.DrawString("clear",60,30);
        }


    }
    int countBlock(){
      int num = 0;
      for(int i =0 ; i < BLOCK_NUM ; i ++ ){
        if(block_alive_flag[i]){
          num++;
        }
      }
      return num;
    }
}

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

スマートデバイスプログラミング②(ランダムにカードを出して得点を競うゲーム)

UnityとGameCanvasのダウンロード

1:UnityHubをDLしてインストール。(一部のwindows環境では拡張子を.exeに変更する必要があります)

2:UnityHubを起動して、Unity2019.3.11f1をインストール。

3:GameCanvasのダウンロード
https://github.com/sfc-sdp/GameCanvas-Unity/
でGameCanvasをダウンロード

Game.csの編集

Assets内のGame.csを別エディタで編集

using Sequence = System.Collections.IEnumerator;

/// <summary>
/// ゲームクラス。
/// 学生が編集すべきソースコードです。
/// </summary>
public sealed class Game : GameBase
{
    // 変数の宣言
    int money;
    const int CARD_TYPE = 10;
    int[] card_count = new int [CARD_TYPE];
    string[] card_name =
         {"A","B","C","D","E","F","G","H","I","J"};
    bool isComplete;
    int new_card ;

    /// <summary>
    /// 初期化処理
    /// </summary>

    ///起動時に一回だけ呼ばれる・・初期化設定用
    public override void InitGame()
    {
      resetValue();
    }

    /// <summary>
    /// 動きなどの更新処理
    /// </summary>

    //1フレームごとに呼ばれる・動きの処理を入れる
    public override void UpdateGame()
    {
        //タップした時の処理
        if (gc.GetPointerFrameCount(0)==1 && ! isComplete) {
             money -= 100;
             if (gc.Random(0,3)==0){
             new_card = gc.Random(0,4);
           }else{
             new_card = gc.Random(5,9);
           }
             card_count[new_card]++;


           for (int i = 0; i < 5; i++) {
             if (card_count[i] > 4){
               isComplete = true;
             }
           }
        }
        //長押しした時の処理
        if(gc.GetPointerFrameCount(0) >= 120){
          resetValue();
        }
    }

    /// <summary>
    /// 描画の処理
    /// </summary>

    //1フレームごとに呼ばれる・描画の処理
    public override void DrawGame()
    {
      gc.ClearScreen();
      gc.SetColor(255,0,0);
      gc.SetFontSize(36);
      gc.DrawString("money:"+money,60, 40);

      if(new_card >= 0){
        gc.DrawString("new:"+card_name[new_card],60, 80);
      }

      for(int i=0 ; i< CARD_TYPE ; i++){
        gc.DrawString(card_name[i] + ":" + card_count[i],60, 120+i*80);
      }

      if(isComplete ){
        gc.DrawString("complete!!",60, 920);
      }

    }
    void resetValue(){
      money = 10000;
      for (int i = 0; i < CARD_TYPE; i++) {
        card_count[i] = 0;
      }
      isComplete = false;
      new_card = -1;
    }
}

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

スマートデバイスプログラミング①(制限時間内にクリックした回数を競争するゲーム)

UnityとGameCanvasのダウンロード

1:UnityHubをDLしてインストール。(一部のwindows環境では拡張子を.exeに変更する必要があります)

2:UnityHubを起動して、Unity2019.3.11f1をインストール。

3:GameCanvasのダウンロード
https://github.com/sfc-sdp/GameCanvas-Unity/
でGameCanvasをダウンロード

Game.csの編集

Assets内のGame.csを別エディタで編集

using Sequence = System.Collections.IEnumerator;

/// <summary>
/// ゲームクラス。
/// </summary>
public sealed class Game : GameBase
{
    // 変数の宣言
    int sec = 0;

    int time = 600;
    int score = 0;

    /// <summary>
    /// 初期化処理
    /// </summary>
    public override void InitGame()
    {
        // キャンバスの大きさを設定します
        gc.SetResolution(720, 1280);
    }

    /// <summary>
    /// 動きなどの更新処理
    /// </summary>
    public override void UpdateGame()
    {
        // 起動からの経過時間を取得します
        sec = (int)gc.TimeSinceStartup;

        time = time - 1;
        if(gc.GetPointerFrameCount(0)==1){
          if(time >= 0){
            score = score + 1;
          }
        }
        if(gc.GetPointerDuration(0) >= 2.0f){
          time =600;
          score =0;
        }
    }

    /// <summary>
    /// 描画の処理
    /// </summary>
    public override void DrawGame()
    {
        // 画面を白で塗りつぶします
        gc.ClearScreen();

        // 0番の画像を描画します
        gc.DrawImage(0, 0, 0);

        // 黒の文字を描画します
        gc.SetColor(0, 0, 0);
        gc.SetFontSize(48);
        gc.DrawString("この文字と青空の画像が", 40, 160);
        gc.DrawString("見えていれば成功です", 40, 270);
        gc.DrawRightString($"{sec}s", 630, 10);

        if(time >= 0 ){
          gc.DrawString("time:"+time,60,0);
        }
        else {
          gc.DrawString("finished!!",60,0);
        }
        gc.DrawString("score:"+score,60,60);
    }
}

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

自分が XAML 系プラットフォームのクラスを覚えるときにしてること

既に大体覚えてる人は対象外です。都度都度コントロールを調べて覚えてるんだけど、覚えることが多すぎて大変な人向け。

XAML 系プラットフォームのコントロールの特徴

継承を使って色々なコントロールが定義されています。
端的にいうと継承ツリーの中で重要なコントロールについて覚えておくと、他でも使いまわしが効いていいです。

UWP & WPF

UWP と WPF はコントロールの構造が似ています。なので覚え方も大体同じです。大まかに全体に共通することと大きく 4 つのカテゴリーに分けて覚えます。

全体に大体共通

  • Style でプロパティの値をまとめて設定できる
  • 依存関係プロパティはバインド出来る
  • Margin の設定とか幅と高さを持ってる

ContentControl

Content プロパティに設定したものをいい感じに表示してくれる。
見た目を整えるなら Content プロパティに何でもいいからオブジェクト突っ込んで ContentTemplate に DataTemplate を設定して見た目整える。

以下のように色々なコントロールがこれに該当。

  • ContentControl
  • Window
  • ToolTip
  • Button
  • CheckBox
  • RadioButton
  • ListBoxItem
  • Label

ItemsControl

複数個のデータを表示するコントロール。ItemsSource プロパティにコレクションを設定(もしくはバインド)して、ItemTemplate で 1 項目ごとの見た目を DataTemplate で定義する。
因みにデータの 1 行単位でみると ContentControl です。

  • ItemsControl
  • ListBox
  • TreeView
  • Menu

厳密にはちょっと違うけど似た雰囲気として使えるものとして DataGrid がある。
これは 1 セル 1 セルが ContentControl みたいな感じで、編集モードとか表示モードとかあってちょっと複雑。こいつは真面目に覚えるしかない。

Panel

Children プロパティに子要素を置いてレイアウトをいい感じにしてくれる。

  • Grid: 行と列を定義して配置する。一番使う。何かレイアウトパネル覚えるならまずこれ。
  • StackPanel: 縦並び、横並びが出来る。Grid 覚えたら次はこれでいいと思う
  • Canvas: 絶対座標指定。そんなに使わないけど、便利なこともある。
  • RelativePanel: UWP のみだけど便利。相対座標での配置もなれれば楽。レスポンシブデザインに対応するのが他の Panel より楽。
  • DockPanel: WPF のみだったかな?(UWP の Toolkit に入ってたかも?) 聖杯レイアウトが出来る。アプリの大枠切るのに便利。
  • WrapPanel: WPF のみ。UWP は Toolkit にある。横並び縦並びで端まで行くと折り返すレイアウト

その他

諦めて個別に覚えよう。

総括

ということで以下のクラスを重点的にリファレンスを見て、どんな機能が定義されるのかをまずみましょう。

  • FrameworkElement
  • Control

↑のクラスは、ほぼすべてのコントロールの先祖にあたるため、ここにあるものは他のコントロールで使えます。覚えて損なし。

次に以下の 2 つのコントロールの使い方を見てみよう。

  • ContentControl
  • ItemsControl

1 項目を表示するコントロールと、複数のデータを表示するコントロールのベースとなるクラス。
大体のコントロールは上の 2 つのどちらから継承してたりすることが多いので、この 2 つを覚えてると、どんなコントロールでも見た目を思い通りに定義しやすくなります。

そして Grid コントロールでのレイアウトの仕方を覚えておけば思った通りの見た目は大体出来ると思います。

Xamarin.Forms

Xamarin.Forms は XAML だけど、UI コントロールが最終的に各プラットフォームのネイティブコントロールに直接マッピングされたりするに関係上かわかりませんが、ContentControl とかがなかったりして勝手が少し違います。
似たコントロールとして ContentView がありますが、そんなに継承しているクラスは多くない感じなので覚えても費用対効果がそんなに高くなかったりします。

でも、ItemsControl と同じノリで使える感じのものは多いです。これは覚える価値あり。

  • ItemsControl
  • CollectionView
  • TaggedPage: ItemsSource とは継承関係にないですが ItemsSource プロパティと ItemTemplate プロパティを持ってる

レイアウトパネルは UWP や WPF と大体同じですが StackPanel じゃなくて StackLayout だったり微妙に名前違うものがあってちょっと戸惑います。
あとは FlexLayout という Web の CSS Flexbox と同じノリでレイアウトを組めるやつがあるので、そこらへんもおさえておくといいかもです。

まとめ

ということで、UI 系の部品の多くはオブジェクト指向の継承を使って作られてるものが XAML 系では多いので、継承のツリーを見て、肝となる機能を実装している親クラスを見つけて、そいつの機能を抑えることで、そのクラスを継承しているクラスの使い方のほとんどを押さえることが出来るので、費用対効果が高くてオススメです。

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

[C#] awaitを含むコードの排他制御

この投稿の前提情報

awaitを含むということはほとんどの場合時間的に長い処理であり、この間ずっと他のスレッドをブロックしっぱなしというものは決して褒められたコードではない。
できるのであれば、その非同期メソッド内の同期処理部分の必要な部分だけをlockするのが最善である。
ここに記載されている情報は、嬉々として採用するべきものではなく、

  • 非同期部分を同時に呼び出してしまうと困ったことがおきる
  • 非同期メソッド呼び出し前後でデータの整合性を保つ必要がある

といった状況化で仕方なく、しぶしぶ採用するべきものになる。

lockブロック内ではawaitできない

通常、排他制御をおこなう場合、lockステートメントを使用する。

readonly object LockHandler = new object();

void Hoge()
{
    lock (LockHandler)
    {
        DoSomething();
    }
}

しかし、このlockブロック内にawaitが含まれるコードはコンパイルエラーが発生する。
というのも、ロックを解放するのはそれを獲得したスレッドでなければならず、await演算子の前と後ろは違うスレッドで実行される可能性があるためだ。

// Compile Error
lock (LockHandler)
{
    await DoSomethingAsync();
}

SempahoreSlimを使う

そこで、ロックの獲得と解放を異なるスレッドで行うことのできる仕掛けが必要になる。
これに適したクラスがSemaphoreSlimであり、これでawaitを含むコードの排他処理ができるようになる。

readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);

public async Task AsynchronousMethodAsync()
{
    await Semaphore.WaitAsync();
    try
    {
        await DoSomethingAsync();
    }
    finally
    {
        Semaphore.Release();
    }
}

もし、同じクラスの同期処理部分でも排他制御をおこないたければ、同じSemaphoreSlimオブジェクトのWaitメソッドを使用すればよい。

public void SynchronousMethod()
{
    Semaphore.Wait();
    try
    {
        DoSomething();
    }
    finally
    {
        Semaphore.Release();
    }
}

SemaphoreSlimの注意点

SemaphoreSlimを使用している場合、lockステートメントと異なり、すでに獲得しているロックを再取得できない。
以下のコードは1を出力して、そのまま停止する。2には到達しない。

readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);

public async Task AsynchronousMethodAsync()
{
    await Semaphore.WaitAsync();
    try
    {
        Console.WriteLine("1");          // 1
        await Semaphore.WaitAsync();
        Console.WriteLine("2");          // 2
    }
    finally
    {
        Semaphore.Release();
    }
}

これが意味するのは、あるSemaphoreSlimオブジェクトでガードされているメソッドから、同じオブジェクトでガードされているメソッドを呼び出せないということだ。
以下のAsynchronousMethodAsync()は、SynchronousMethod()Semaphore.Wait()で処理が停止し、それ以上処理が継続されない。

public async Task AsynchronousMethodAsync()
{
    await Semaphore.WaitAsync();
    try
    {
        SynchronousMethod();
        await ...
    }
    finally
    {
        Semaphore.Release();
    }
}

public void SynchronousMethod()
{
    Semaphore.Wait();
    ...
}

IDisposableを実装するべきか

SemaphoreSlimIDisposableを実装している。そして、この型をインスタンスフィールドに含むクラスは一般論で言えばIDisposableを実装し、そこでSepamhoreSlimオブジェクトをDisposeするべきだということになる。
つまり、排他制御しているオブジェクトを使いおわったらDispose()する必要がある。lockステートメントで済んでいたクラスと比べて、とても面倒だ。

安心して欲しい。SemaphoreSlimのソースを追えばわかることだが、AvailableWaitHandleプロパティを使用しない場合、そのDispose()は内部の変数にnullを代入する以外の仕事はおこなわれない。
そして、今回の要件ではこのプロパティは不要なので、SemaphoreSlimオブジェクトをDisposeしなくても実質的な問題は生じない。

もちろん未来永劫そうであると保証があるわけではない。
どうするべきかという問いには、「IDisposableを実装するべきだ」と回答するしかない。
だから、コードレビューで原理主義者に追求を受けそうだというのなら、IDisposableを実装することになるだろう。

現実的なところでは、SemaphoreSlimのラッパークラスを作り、AvailableWaitHandleを呼び出せなくしてしまうという手がある。
うるさいコード分析ツールを黙らせるのも楽になるだろう。コンストラクタの引数(1, 1)を隠蔽できるようになるのもいい。

AvailableWaitHandleのないSemaphoreSlimを再実装してしまう方法もある。
何かまちがっている気もするが、こわいコードレビュアーをかかえたチームでは歓迎されるのではないだろうか。

AsyncLockとその注意点

https://www.atmarkit.co.jp/ait/articles/1411/18/news135.html にあるように、SemaphoreSlimそのものを使うのではなく、それを使いやすくしたクラスを使用するという方法がある。

readonly AsyncLock LockHandler = new AsyncLock();

public async Task AsynchronousMethodAsync()
{
    using (await LockHandler.LockAsync())
    {
        await DoSomethingAsync();
    }
}

簡潔で美しい方法だが、awaitを忘れusing (_LockHandler.LockAsync())としてしまうと、ロックされている場合に実行中のTaskDisposeしようとして例外を投げるコードができあがってしまう。
困ったことに、非同期メソッドの呼び出しがawaitされてないという警告も出力されない。

若干一名の尊い犠牲のもとになりたっている情報なので、もし採用するのなら注意してほしい。
犠牲担当の意見としては、二度と利用する気はない。

参考にした情報

非同期:awaitを含むコードをロックするには?(SemaphoreSlim編)[C#、VB]
https://www.atmarkit.co.jp/ait/articles/1411/11/news117.html

非同期:awaitを含むコードをロックするには?(AsyncLock編)[C#、VB]
https://www.atmarkit.co.jp/ait/articles/1411/18/news135.html

SemaphoreSlimのソース
https://source.dot.net/#System.Private.CoreLib/SemaphoreSlim.cs

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

【.NetCore】ViewComponentとAjaxでSPA化する【jQuery】

ViewComponentとAjaxでSPAっぽい挙動を作る

ちょっとしたアクションで画面遷移すると、画面がチラチラして鬱陶しいという要望があった。
とりあえず、あるモノでサクッと直してみる。

Core2.1のサンプルプロジェクトをSPAっぽくしてみる

View直下にComponentsというフォルダを作成し、その中にViewファイルを移動させる。
※Home.cshtmlはindex.cshtmlをコピーして名前を変えただけ
キャプチャ.PNG

index.htmlの最初と最後に書き換え用のdivタグ追加(updateView)

Index.html
<div id="updateView">
    <div class="row">
        <div class="col-md-3">
--省略--
        </div>
    </div>
</div>

コントローラーにViewComponentとGetアクションを追加していく

HomeController.cs
public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpGet]
        public IActionResult About()
        {
            return ViewComponent("AboutView");
        }

        [HttpGet]
        public IActionResult Contact()
        {
            return ViewComponent("ContactView");
        }

        [HttpGet]
        public IActionResult Privacy()
        {
            return ViewComponent("PrivacyView");
        }


        [HttpGet]
        public IActionResult Home()
        {
            return ViewComponent("HomeView");
        }

    }


    [ViewComponent(Name = "AboutView")]
    public class AboutView : Microsoft.AspNetCore.Mvc.ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View("Views/Home/Components/About.cshtml");
        }
    }

    [ViewComponent(Name = "ContactView")]
    public class ContactView : Microsoft.AspNetCore.Mvc.ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View("Views/Home/Components/Contact.cshtml");
        }
    }

    [ViewComponent(Name = "PrivacyView")]
    public class PrivacyView : Microsoft.AspNetCore.Mvc.ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View("Views/Home/Components/Privacy.cshtml");
        }
    }

    [ViewComponent(Name = "HomeView")]
    public class HomeView : Microsoft.AspNetCore.Mvc.ViewComponent
    {
        public IViewComponentResult Invoke()
        {
            return View("Views/Home/Components/Home.cshtml");
        }
    }

Layoutファイルのlistの部分を少し改造する。
移動しないのでhref消して、代わりに振り分けでつかうIDを付けます。

_Layout.cshtml
                <ul class="nav navbar-nav">
                    <li><a id="Home">Home</a></li>
                    <li><a id="About">About</a></li>
                    <li><a id="Contact">Contact</a></li>
                    <li><a id="Privacy">Privacy</a></li>
                </ul>

html取得用のjsを追加

_Layout.cshtml
        $('a').click(function () {
            var action = $(this).attr("id");
            $.get('/Home/' + action)
            .done(function (data) {
                $("#updateView").fadeOut(500, function fadeOut() {
                    $("#updateView").html(data);
                    $("#updateView").fadeIn(500);
                });
            });
        });

これで完成
ダウンロード (1).gif

fadeさせてるので、いい感じに画面がヌルっと切り替わるはずです。

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

Unityど素人の奮闘記

Unityでドミノ倒し作ってみた.

今回はUnityの物理エンジンを使ってドミノ倒しを作ってみました.
ただ同じ大きさのドミノを倒しても面白くないと思いn番煎じながら1.5倍の大きさのドミノを倒していくものにしました.
参考:https://estorypost.com/social-network/youtube/domino-can-topple-1-5-times-its-size/
もはやリアルで示されているものをわざわざやる必要もないのですが笑

Step1 地面をつくる

Createから[3D Object]の[Plane]もしくは[Terrain]を選択します.
今回は自分自身の復習の意味もあってTerrainを選択しました.
スクリーンショット 2020-05-24 21.12.57.png
Terrainを使う際の注意はTerrainの中央がシーンビューの中央とずれていることです.
一辺が500の正方形なのでtransformでPositionをx=-250,y=0,z=-250にしておきましょう.

Step2 自然をつくる

AssetStoreからStandardAssetsをインストールしてください.
驚くほど簡単に山や木,湖がつくれてびっくりします.
スクリーンショット 2020-05-24 21.23.44.png
今回は一応全てインポートしておきましょう.
スクリーンショット 2020-05-24 21.31.00.png
山や木を作ることは非常に簡単でTerrainから[Raise or Lower]を選択して左クリックで山ができます.
山をへこませたい時はシフトキーを押しながら左クリックをすればいいです.
木も同様に木のマークをクリックしインポートしたStandardAssetsの素材をEditから加えてタッチすれば出来ます(適当)
山の表面,地面の表面の素材も同様にもはやドラッグするだけです.

Step3 ドミノ作成

ドミノの作成で注意することは一つだけです.
それは各辺の長さをr倍した時,体積はr^3倍となることです.そのことにだけ注意すれば問題ないと思います.
あとはAdd ComponentでRigidBodyを追加してあげましょう.
Cubeからドミノを作成した場合,Box Colliderは最初から追加されています.
あとは一番小さなドミノを傾けて...
と言いたいところですがまだ何もコードを書いていないので書きましょう.
すべてのドミノが倒れた時に歓声が起こるコードを書きます.

Step4 歓声作成

スクリーンショット 2020-05-24 21.58.51.png
上のようなソースコードでpublic変数としてAudioを追加できるようにしました.
Audioは効果音ラボさんからダウンロードさせていただきました.
効果音ラボ(https://soundeffect-lab.info/)
あとはCreateから[CreateEmpty]を選択し,[Timekeep]と名前を変更しました.
オブジェクトに先ほどのTimekeepスクリプトを追加し,音声も追加しました.
スクリーンショット 2020-05-25 11.08.26.png

完成!

これからも勉強していきます...

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

【Unity】非同期処理を理解する〜async/await編〜

async/await とは

  • C# の機能として用意されている
    • C# 5.0の新機能
    • Unity 2018 から C# 6.0 に対応した(それまでは C# 4.0 でした。)
    • Task クラスに対して使うものという軽い認識(本当は INotifyCompletion インターフェースの IsCompletedGetResult
  • Unity で .NET 4.x を選択すれば使用可能
  • async = asynchronous = 非同期
    • await を使うメソッドにつけなければならないキーワード
    • async をつけただけでは普通のメソッドと挙動は変わらない
  • await
    • 非同期メソッドを呼び出し、完了するまで実行を中断するキーワード
  • 戻り値を取得できる(コルーチンは無理)
  • 並列処理だけど、普通のメソッドの呼び出しと同じようにかける = 可読性が上がる

基本的な構文は、

async 戻り値の型 メソッド名(引数)
{
    await ~~~~
}    

戻り値は、基本的に Task / Task<T> 型と考えて問題ないかと思います。
(といいながら、サンプルは void で書いたりしています…)

機能

Task.Delay(Int32 millisecondsDelay)

引数で指定されたミリ秒待機します。

using System.Threading.Tasks;
using UnityEngine;


public class Test : MonoBehaviour
{
    void Start()
    {
        AsyncSample1();
    }

    async void AsyncSample1()
    {
        Debug.Log("AsyncSample1 Start.");
        await Task.Delay(1000);
        Debug.Log("AsyncSample1 End.");
    }
}

今回のサンプルでは、1000ミリ秒 = 1秒 待機するようにしたので、実行結果は以下のようになります。
image.png

ContinueWith

直列処理できます。
非同期処理のメソッドも、ContinueWith でつなぐことで、直列化することができます。

ちなみに ContinueWith使わずに、普通に並列的に処理にしたければ、下のようになイメージになります。

using System.Threading.Tasks;
using UnityEngine;


public class Test : MonoBehaviour
{
    void Start()
    {
        AsyncSample1();
        AsyncSample2();
    }

    async void AsyncSample1()
    {
        Debug.Log("AsyncSample1 Start.");
        await Task.Delay(1000);
        Debug.Log("AsyncSample1 End.");
    }

    async void AsyncSample2()
    {
        Debug.Log("AsyncSample2 Start.");
        await Task.Delay(1000);
        Debug.Log("AsyncSample2 End.");
    }
}

並列なので、
1. AsyncSample1 Start.
2. AsyncSample2 Start.
3. AsyncSample1 End.
4. AsyncSample2 End.
という結果になります。

image.png

ContinueWith を使って直列処理(AsyncSample1 を実行完了後、 AsyncSample2 を実行)するサンプルは、

using System.Threading.Tasks;
using UnityEngine;


public class Test : MonoBehaviour
{
    void Start()
    {
        AsyncSample1().ContinueWith(_ => AsyncSample2());
    }

    async Task AsyncSample1()
    {
        Debug.Log("AsyncSample1 Start.");
        await Task.Delay(1000);
        Debug.Log("AsyncSample1 End.");
    }

    async Task AsyncSample2()
    {
        Debug.Log("AsyncSample2 Start.");
        await Task.Delay(1000);
        Debug.Log("AsyncSample2 End.");
    }
}

注意点は、メソッドの返り値の型が Task になっていることくらいです。

  1. AsyncSample1 Start.
  2. AsyncSample1 End.
  3. AsyncSample2 Start.
  4. AsyncSample2 End. という結果になります。

image.png

Task.Run()

引数として与えられた処理を別スレッドで実行します。
用途として、重い処理を非同期にしたいときに使います。

ちょっと良い例が思いつかないので、超簡単なサンプルだけ書きます。

Task.Run(()  =>  Debug.Log("重い処理..."));

Task.WhenAll()

指定された Task が全て完了してから Task を実行することができます。

using System.Threading.Tasks;
using UnityEngine;


public class Test : MonoBehaviour
{
    async void Start()
    {
        await Task.WhenAll(AsyncSample1(), AsyncSample2());
        Debug.Log("All Completed.");
    }

    async Task AsyncSample1()
    {
        Debug.Log("AsyncSample1 Start.");
        await Task.Delay(1000);
        Debug.Log("AsyncSample1 End.");
    }

    async Task AsyncSample2()
    {
        Debug.Log("AsyncSample2 Start.");
        await Task.Delay(1000);
        Debug.Log("AsyncSample2 End.");
    }
}

AsyncSample1(), AsyncSample2() の実行が完了したら、 All Completed. を出力するようなサンプルです。
実行結果は、
image.png

Task<T>

戻り値が欲しい場合は Task<T> を使います。

using System.Threading.Tasks;
using UnityEngine;


public class Test : MonoBehaviour
{
    async void Start()
    {
        var str = await AsyncSample1();
        Debug.Log(str);
    }

    async Task<string> AsyncSample1()
    {
        Debug.Log("AsyncSample1 Start.");
        await Task.Delay(1000);
        return "AsyncSample1 End.";
    }
}

AsyncSample1() の結果が返ってくるまで、Debug.Log されると困るので、 await する必要があります。
(待ち受けするイメージです。)
結果は、
image.png

終わりに

Unity 初心者なので、間違いがあったら教えてくれるとありがたいです。
Unity で Task 使うなら、 UniTask 使おう。(理由はいまいち知らない…)
理由は UniTask の記事を書くときに、調べたいと思います。

参考文献

https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.tasks.task?view=netcore-3.1
https://torikasyu.com/?p=1554
https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.tasks.task.whenall?view=netcore-3.1
https://www.slideshare.net/UnityTechnologiesJapan/unite-tokyo-2018asyncawait

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

C#でMarkdownを表示するライブラリMarkDigの紹介

概要

MarkDigはC#でMarkdownを表示できるライブラリです。
同様の機能を持つ、Marked.NET、MarkdownSharpよりも高速で動くらしいです。

Webアプリで使う場合はHTMLに、WPFで使う場合は専用ControlかRichTextBoxに変換して表示できます。

HTMLに変換する場合

Markdown文字列をHTMLに変換するのはMarkdig.Markdown.ToHtml(元Markdown文字列)とするだけです。

以下のコードでは加えて、入力文字列をリソースファイルから取得して、生成したHTMLをファイルへ出力、ソースコードを見やすくするMarkdig.SyntaxHighlighting拡張を導入しました。

private void CreateHtml()
{
    var pipeline = new MarkdownPipelineBuilder()
        .UseAdvancedExtensions()
        .UseSyntaxHighlighting()
        .Build();

    string sourceText = Properties.Resources.MarkDownText;
    string markdownText = Markdig.Markdown.ToHtml(sourceText, pipeline);
    const string ouputPath = "result.html";
    File.WriteAllText(ouputPath, markdownText);
}

出力結果は以下を参照してください。
出力結果HTMLファイル

WPFで表示する場合

WPFで表示する場合はFlowDocumentに変換してRichTextBoxに入力してもよいですが、Markdig-WPF拡張を導入してMarkDownViewerを使用するほうが手軽です。
<markdig:MarkdownViewer Markdown="元Markdown文字列" />で表示できます。

以下のコードでは加えて、初期入力文字列をリソースファイルから取得して、TextBoxに指定。TextBoxのTextを変更するとMarkdownViewerに反映されるようにしました。またHyperLinkをクリック時にブラウザで開く処理も追加しました。

MainWindow.xaml
<Window
   x:Class="MarkDigTest.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:markdig="clr-namespace:Markdig.Wpf;assembly=Markdig.Wpf"
   xmlns:properties="clr-namespace:MarkDigTest.Properties"
   Title="MainWindow" Width="800" Height="650">
   <FrameworkElement.CommandBindings>
      <CommandBinding Command="{x:Static markdig:Commands.Hyperlink}" Executed="OpenHyperlink" />
   </FrameworkElement.CommandBindings>
   <UniformGrid Columns="2">
      <TextBox
         x:Name="sourceText"
         AcceptsReturn="True"
         Text="{x:Static properties:Resources.MarkDownText}" />
      <markdig:MarkdownViewer x:Name="Viewer" 
         Markdown="{Binding Text, ElementName=sourceText}" />
   </UniformGrid>
</Window>
MainWindow.cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void OpenHyperlink(object sender, ExecutedRoutedEventArgs e)
    {
        Process.Start(new ProcessStartInfo("cmd", $"/c start {e.Parameter}") { CreateNoWindow = true });
    }
}

動作時のイメージは以下です。
左のTextBoxに入力すると、右のMarkdownViewerに反映されます。

image.png

全体コード

WPF版の全体コードを以下に置いておきます。
https://github.com/soi013/MarkDigTest/tree/master

環境

VisualStudio 2019
C# 8c
.NET Core 3.1

MarkDigTest.csproj
<ItemGroup>
  <PackageReference Include="Markdig" Version="0.18.0" />
  <PackageReference Include="Markdig.Wpf" Version="0.3.1" />
</ItemGroup>

参考

https://github.com/lunet-io/markdig

Markdownのサンプルは一部以下のリンクを元にしています。
https://qiita.com/tbpgr/items/989c6badefff69377da7

HTMLファイルを表示するためにGitHub.Pagesを使いました。
https://qiita.com/Yuki_Yamashina/items/5d8208c450195b65344c

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

C# 勉強(3) ファイル入力

ファイルを入力し店舗別売上を集計する。

1、プログラム

:Program.cs
using System;
using System.Collections.Generic;
using System.IO;


namespace Sec0225
{
    class Program
    {
        static void Main(string[] args)
        {
            SalesCounter sales = new SalesCounter(ReadSales("sales.csv"));
            Dictionary<string,int> amountPerStore = sales.GetPerStoreSales();
            foreach(KeyValuePair<string,int>obj in amountPerStore){
                Console.WriteLine("{0}{1}",obj.Key,obj.Value);
            }
        }
        static List<Sale>ReadSales(string filePath){
            List<Sale>sales = new List<Sale>();
            string[] lines = File.ReadAllLines(filePath);       //テキストファイル読む
            foreach(string line in lines){                      //1行づつ処理
                string[]items = line.Split(',');                //行を分解
                Sale sale = new Sale{                           //分解したデータからオブジェクト作成
                    ShopName = items[0],
                    ProductCategory = items[1],
                    Amount = int.Parse(items[2])
                };
                sales.Add(sale);                                //オブジェクトをリストに追加
            }
            return sales;                                       //結果を返す
        }
    }
}

2.クラス

:Sale.cs
using System;

namespace Sec0225{
    public class Sale{
        public string ShopName { get; set; }
        public string ProductCategory { get; set; }
        public int Amount { get; set; }
    }
}
:SalesCounter.cs
using System;
using System.Collections.Generic;

namespace Sec0225{
    public class SalesCounter{
        private List<Sale> _sales;

        public SalesCounter(List<Sale>sales){
            _sales = sales;
        }
        public Dictionary<string,int> GetPerStoreSales(){
            Dictionary<string,int> dict = new Dictionary<string, int>();
            foreach(Sale sale in _sales){
                if(dict.ContainsKey(sale.ShopName))
                    dict[sale.ShopName]+=sale.Amount;
                else
                    dict[sale.ShopName]=sale.Amount;
            }
            return dict;
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Excel, Word, PowerPointで開いているファイルを取得して一覧表示する

Reloadボタンで再読み込みします。
ListViewのアイテムをダブルクリックすると、そのフォルダを開きます。

image.png

コンパイル方法

compile.bat
csc ^
 /r:C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.Excel\15.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Excel.dll ^
 /r:C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.Word\15.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Word.dll ^
 /r:C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.PowerPoint\15.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.PowerPoint.dll ^
 /r:C:\Windows\assembly\GAC_MSIL\office\15.0.0.0__71e9bce111e9429c\Office.dll ^
 %*

compile.bat GetDirOfOfficeOpening.cs
を実行するとコンパイルされます。(dllの場所は環境により異なる可能性があります。)

ソース

機能拡張しようとして作成中の部分がコメントアウトで残っているのはご容赦ください。

GetDirOfOfficeOpening.cs
using System;
using System.Drawing;
using System.IO;
//using System.Collections.Generic;
//using System.Reflection;
using System.Runtime.CompilerServices; // to use [MethodImpl(MethodImplOptions.NoInlining)]
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
//using Microsoft.Office.Core;

class GetDirOfOfficeOpening : Form
{
    // column index of lsvFiles
    //readonly int CI_FileName = 1;
    //readonly int CI_Path = 2;

    ListView lsvFiles;
    Button btnReload;
    //Button btnExportAllLinks;

    void ReloadList()
    {
        lsvFiles.BeginUpdate();
        lsvFiles.Items.Clear();
        try {
            AppendExcelFileList();
            AppendWordFileList();
            AppendPowerPointFileList();
        }
        finally {
            lsvFiles.EndUpdate();
        }
        // GC.Collect(); GC.WaitForPendingFinalizers();
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void AppendExcelFileList()
    {
        Excel.Application oExcelApp;

        try {
            oExcelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
        }
        catch(COMException) {
            return; // Excelが起動していない、もしくは不明なエラー
        }

        Excel.Workbooks oWorkBooks = (Excel.Workbooks)oExcelApp.Workbooks;

        foreach ( Excel.Workbook book in oWorkBooks ) {
            string t = book.FullName;
            if ( t.IndexOf("/") >= 0 || t.IndexOf("\\") >= 0 ) {
                // pathあり
                lsvFiles.Items.Add(MakeLsvItem("Excel", Path.GetFileName(t), Path.GetDirectoryName(t)));
            }
            else {
                lsvFiles.Items.Add(MakeLsvItem("Excel", t, ""));
            }
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void AppendWordFileList()
    {
        Word.Application oWordApp;

        try {
            oWordApp = (Word.Application)Marshal.GetActiveObject("Word.Application");
        }
        catch(COMException) {
            return; // Wordが起動していない、もしくは不明なエラー
        }

        Word.Documents oWordDocs = (Word.Documents)oWordApp.Documents;

        foreach ( Word.Document doc in oWordDocs ) {
            string t = doc.FullName;
            if ( t.IndexOf("/") >= 0 || t.IndexOf("\\") >= 0 ) {
                // pathあり
                lsvFiles.Items.Add(MakeLsvItem("Word", Path.GetFileName(t), Path.GetDirectoryName(t)));
            }
            else {
                lsvFiles.Items.Add(MakeLsvItem("Word", t, ""));
            }
        }
    }


    [MethodImpl(MethodImplOptions.NoInlining)]
    void AppendPowerPointFileList()
    {
        PowerPoint.Application oPptApp;

        try {
            oPptApp = (PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application");
        }
        catch(COMException) {
            return; // PowerPointが起動していない、もしくは不明なエラー
        }

        PowerPoint.Presentations oPptPresentations = (PowerPoint.Presentations)oPptApp.Presentations;

        foreach ( PowerPoint.Presentation presen in oPptPresentations ) {
            string t = presen.FullName;
            if ( t.IndexOf("/") >= 0 || t.IndexOf("\\") >= 0 ) {
                // pathあり
                lsvFiles.Items.Add(MakeLsvItem("PowerPoint", Path.GetFileName(t), Path.GetDirectoryName(t)));
            }
            else {
                lsvFiles.Items.Add(MakeLsvItem("PowerPoint", t, ""));
            }
        }
    }

/*
    [MethodImpl(MethodImplOptions.NoInlining)]
    void CreateShortcut(string lnkDest, string saveDest, ref dynamic shell)
    {
        // WshShellを作成
        if (shell == null) {
            var type = Type.GetTypeFromProgID("WScript.Shell");
            shell = Activator.CreateInstance(type);
        }
        // IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
        dynamic shortcut = shell.CreateShortcut(saveDest);

        // リンク先
        shortcut.TargetPath = lnkDest;
        // shortcut.Arguments = "/a /b /c";
        // shortcut.WorkingDirectory = Application.StartupPath;
        // 実行時の大きさ 1が通常、3が最大化、7が最小化
        shortcut.WindowStyle = 1;
        // shortcut.Description = "xxx";
        // shortcut.IconLocation = Application.ExecutablePath + ",0";
        // ショートカットを作成
        shortcut.Save();
    }


    [MethodImpl(MethodImplOptions.NoInlining)]
    void ExportAllLinks(string saveDestPath)
    {
        dynamic shell = null;
        foreach (ListViewItem item in lsvFiles.Items) {
            string basePath = item.SubItem[CI_Path].Text;
            if (basePath != "") {
                string fileName = item.SubItem[CI_FileName].Text;
                string lnkDest = Path.Combine(basePath, fileName);
                string saveDest = Path.Combine(saveDestPath, fileName+".lnk");
                CreateShortcut(lnkDest, saveDest, ref shell);
            }
        }
    }


    void ExportAllLinksWithDialog()
    {
        //FolderBrowserDialogクラスのインスタンスを作成
        FolderBrowserDialog fbd = new FolderBrowserDialog();

        //上部に表示する説明テキストを指定する
        fbd.Description = "フォルダを指定してください。";
        //ルートフォルダを指定する
        //デフォルトでDesktop
        fbd.RootFolder = Environment.SpecialFolder.Desktop;
        //最初に選択するフォルダを指定する
        //RootFolder以下にあるフォルダである必要がある
        fbd.SelectedPath = Environment.SpecialFolder.Desktop; // @"C:\Windows";
        //ユーザーが新しいフォルダを作成できるようにする
        //デフォルトでTrue
        fbd.ShowNewFolderButton = true;

        //ダイアログを表示する
        if (fbd.ShowDialog(this) == DialogResult.OK) {
            ExportAllLinks(fbd.SelectedPath);
        }
    }
    */

    ListViewItem MakeLsvItem(string appName, string fileName, string path)
    {
        return new ListViewItem(new string[]{appName, fileName, path});
    }


    GetDirOfOfficeOpening()
    {
        Controls.Add(btnReload = new Button(){
            Text = "Reload",
            Location = new Point(0, 0),
            Width = 100
        });
        btnReload.Click += (s,e)=>{ReloadList();};

/*
        Controls.Add(btnExportAllLinks = new Button(){
            Text = "Reload",
            Location = new Point(0, 0),
            Width = 100
        });
        btnExportAllLinks.Click += (s,e)=>{ExportAllLinksWithDialog();};
*/

        Controls.Add(lsvFiles = new ListView(){
            View = View.Details,
            FullRowSelect = true,
            HideSelection = false,
            MultiSelect = false,
            GridLines = true,
            Location = new Point(0,30),
            Size = new Size(100,100)
        });
        lsvFiles.Columns.Add("Application",  80, HorizontalAlignment.Left);
        lsvFiles.Columns.Add("FileName",  100, HorizontalAlignment.Left);
        lsvFiles.Columns.Add("Path", 400, HorizontalAlignment.Left);
        lsvFiles.MouseDoubleClick += lsvFiles_MouseDoubleClick;

        ClientSize = new Size(600 ,300);

        Load      += (s,e)=>{MyResize();ReloadList();};
        Resize    += (s,e)=>{MyResize();};
        ResizeEnd += (s,e)=>{MyResize();};
    }

    void MyResize()
    {
        int h = ClientSize.Height - lsvFiles.Top;
        lsvFiles.Size = new Size(ClientSize.Width, (h<50)?50:h);
    }

    void lsvFiles_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        ListViewHitTestInfo info = lsvFiles.HitTest(e.Location);
        if ( info.SubItem != null && info.SubItem.Text != "" ) {
            System.Diagnostics.Process.Start(info.SubItem.Text);
        }
    }

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

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

Excel, Word, PowerPointで開いているファイルのフルパスを取得して一覧表示・ジャンプできるようにする

Reloadボタンで再読み込みします。
ListViewのアイテムをダブルクリックすると、そのフォルダを開きます。

image.png

コンパイル方法

compile.bat
csc ^
 /r:C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.Excel\15.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Excel.dll ^
 /r:C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.Word\15.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Word.dll ^
 /r:C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.PowerPoint\15.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.PowerPoint.dll ^
 /r:C:\Windows\assembly\GAC_MSIL\office\15.0.0.0__71e9bce111e9429c\Office.dll ^
 %*

compile.bat GetDirOfOfficeOpening.cs
を実行するとコンパイルされます。(dllの場所は環境により異なる可能性があります。)

ソース

機能拡張しようとして作成中の部分がコメントアウトで残っているのはご容赦ください。

GetDirOfOfficeOpening.cs
using System;
using System.Drawing;
using System.IO;
//using System.Collections.Generic;
//using System.Reflection;
using System.Runtime.CompilerServices; // to use [MethodImpl(MethodImplOptions.NoInlining)]
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
//using Microsoft.Office.Core;

class GetDirOfOfficeOpening : Form
{
    // column index of lsvFiles
    //readonly int CI_FileName = 1;
    //readonly int CI_Path = 2;

    ListView lsvFiles;
    Button btnReload;
    //Button btnExportAllLinks;

    void ReloadList()
    {
        lsvFiles.BeginUpdate();
        lsvFiles.Items.Clear();
        try {
            AppendExcelFileList();
            AppendWordFileList();
            AppendPowerPointFileList();
        }
        finally {
            lsvFiles.EndUpdate();
        }
        // GC.Collect(); GC.WaitForPendingFinalizers();
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void AppendExcelFileList()
    {
        Excel.Application oExcelApp;

        try {
            oExcelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
        }
        catch(COMException) {
            return; // Excelが起動していない、もしくは不明なエラー
        }

        Excel.Workbooks oWorkBooks = (Excel.Workbooks)oExcelApp.Workbooks;

        foreach ( Excel.Workbook book in oWorkBooks ) {
            string t = book.FullName;
            if ( t.IndexOf("/") >= 0 || t.IndexOf("\\") >= 0 ) {
                // pathあり
                lsvFiles.Items.Add(MakeLsvItem("Excel", Path.GetFileName(t), Path.GetDirectoryName(t)));
            }
            else {
                lsvFiles.Items.Add(MakeLsvItem("Excel", t, ""));
            }
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void AppendWordFileList()
    {
        Word.Application oWordApp;

        try {
            oWordApp = (Word.Application)Marshal.GetActiveObject("Word.Application");
        }
        catch(COMException) {
            return; // Wordが起動していない、もしくは不明なエラー
        }

        Word.Documents oWordDocs = (Word.Documents)oWordApp.Documents;

        foreach ( Word.Document doc in oWordDocs ) {
            string t = doc.FullName;
            if ( t.IndexOf("/") >= 0 || t.IndexOf("\\") >= 0 ) {
                // pathあり
                lsvFiles.Items.Add(MakeLsvItem("Word", Path.GetFileName(t), Path.GetDirectoryName(t)));
            }
            else {
                lsvFiles.Items.Add(MakeLsvItem("Word", t, ""));
            }
        }
    }


    [MethodImpl(MethodImplOptions.NoInlining)]
    void AppendPowerPointFileList()
    {
        PowerPoint.Application oPptApp;

        try {
            oPptApp = (PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application");
        }
        catch(COMException) {
            return; // PowerPointが起動していない、もしくは不明なエラー
        }

        PowerPoint.Presentations oPptPresentations = (PowerPoint.Presentations)oPptApp.Presentations;

        foreach ( PowerPoint.Presentation presen in oPptPresentations ) {
            string t = presen.FullName;
            if ( t.IndexOf("/") >= 0 || t.IndexOf("\\") >= 0 ) {
                // pathあり
                lsvFiles.Items.Add(MakeLsvItem("PowerPoint", Path.GetFileName(t), Path.GetDirectoryName(t)));
            }
            else {
                lsvFiles.Items.Add(MakeLsvItem("PowerPoint", t, ""));
            }
        }
    }

/*
    [MethodImpl(MethodImplOptions.NoInlining)]
    void CreateShortcut(string lnkDest, string saveDest, ref dynamic shell)
    {
        // WshShellを作成
        if (shell == null) {
            var type = Type.GetTypeFromProgID("WScript.Shell");
            shell = Activator.CreateInstance(type);
        }
        // IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
        dynamic shortcut = shell.CreateShortcut(saveDest);

        // リンク先
        shortcut.TargetPath = lnkDest;
        // shortcut.Arguments = "/a /b /c";
        // shortcut.WorkingDirectory = Application.StartupPath;
        // 実行時の大きさ 1が通常、3が最大化、7が最小化
        shortcut.WindowStyle = 1;
        // shortcut.Description = "xxx";
        // shortcut.IconLocation = Application.ExecutablePath + ",0";
        // ショートカットを作成
        shortcut.Save();
    }


    [MethodImpl(MethodImplOptions.NoInlining)]
    void ExportAllLinks(string saveDestPath)
    {
        dynamic shell = null;
        foreach (ListViewItem item in lsvFiles.Items) {
            string basePath = item.SubItem[CI_Path].Text;
            if (basePath != "") {
                string fileName = item.SubItem[CI_FileName].Text;
                string lnkDest = Path.Combine(basePath, fileName);
                string saveDest = Path.Combine(saveDestPath, fileName+".lnk");
                CreateShortcut(lnkDest, saveDest, ref shell);
            }
        }
    }


    void ExportAllLinksWithDialog()
    {
        //FolderBrowserDialogクラスのインスタンスを作成
        FolderBrowserDialog fbd = new FolderBrowserDialog();

        //上部に表示する説明テキストを指定する
        fbd.Description = "フォルダを指定してください。";
        //ルートフォルダを指定する
        //デフォルトでDesktop
        fbd.RootFolder = Environment.SpecialFolder.Desktop;
        //最初に選択するフォルダを指定する
        //RootFolder以下にあるフォルダである必要がある
        fbd.SelectedPath = Environment.SpecialFolder.Desktop; // @"C:\Windows";
        //ユーザーが新しいフォルダを作成できるようにする
        //デフォルトでTrue
        fbd.ShowNewFolderButton = true;

        //ダイアログを表示する
        if (fbd.ShowDialog(this) == DialogResult.OK) {
            ExportAllLinks(fbd.SelectedPath);
        }
    }
    */

    ListViewItem MakeLsvItem(string appName, string fileName, string path)
    {
        return new ListViewItem(new string[]{appName, fileName, path});
    }


    GetDirOfOfficeOpening()
    {
        Controls.Add(btnReload = new Button(){
            Text = "Reload",
            Location = new Point(0, 0),
            Width = 100
        });
        btnReload.Click += (s,e)=>{ReloadList();};

/*
        Controls.Add(btnExportAllLinks = new Button(){
            Text = "Reload",
            Location = new Point(0, 0),
            Width = 100
        });
        btnExportAllLinks.Click += (s,e)=>{ExportAllLinksWithDialog();};
*/

        Controls.Add(lsvFiles = new ListView(){
            View = View.Details,
            FullRowSelect = true,
            HideSelection = false,
            MultiSelect = false,
            GridLines = true,
            Location = new Point(0,30),
            Size = new Size(100,100)
        });
        lsvFiles.Columns.Add("Application",  80, HorizontalAlignment.Left);
        lsvFiles.Columns.Add("FileName",  100, HorizontalAlignment.Left);
        lsvFiles.Columns.Add("Path", 400, HorizontalAlignment.Left);
        lsvFiles.MouseDoubleClick += lsvFiles_MouseDoubleClick;

        ClientSize = new Size(600 ,300);

        Load      += (s,e)=>{MyResize();ReloadList();};
        Resize    += (s,e)=>{MyResize();};
        ResizeEnd += (s,e)=>{MyResize();};
    }

    void MyResize()
    {
        int h = ClientSize.Height - lsvFiles.Top;
        lsvFiles.Size = new Size(ClientSize.Width, (h<50)?50:h);
    }

    void lsvFiles_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        ListViewHitTestInfo info = lsvFiles.HitTest(e.Location);
        if ( info.SubItem != null && info.SubItem.Text != "" ) {
            System.Diagnostics.Process.Start(info.SubItem.Text);
        }
    }

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

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