😆

C#とPowerAutomateでコメントを画面に表示する発表者アプリ作ってみた

2021/05/11に公開

発表を盛り上げたい

オンラインが主流になってきた今、発表の場もオンラインであることが多いのではないでしょうか。
オンラインの発表って相手の反応がわかりづらいので、発表者にとってはメンタルに来るものがあったり。
聞き手も、TeamsやZoomのコメント欄だと名前が出るので、大人数の発表の場で「888888888」とか打つのって少し勇気がりますよね。

そこで、フォーム入力から発表者の画面にコメントを表示するような仕組みを勉強がてら作ってみましたのでご紹介します。

完成デモ

フォームにコメントを入力

聞き手はMicrosoft Formsからコメントを発信することができます。
Formsの弱点は一個コメントを送ってしまうと「記入ありがとう」ページに飛んでしまうので、コメントの連投ができないこと。
htmlやcssが触れる人は一画面だけ作ってしまってもいいかもしれません。
今回はPower Automateの勉強も兼ねているのでFormsのままやってみます。

発表者の画面にコメントが流れる

聞き手が送ったコメントは右から流れてきます。
「88888888888」などであふれた画面を見ることで、発表者は精神的に安心することでしょう。
ただ不適切なコメントも画面に出てしまうのでその点だけ注意です。
Formsを使った場合、誰がコメントしたかが記録されるので、発表後にしばき倒しましょう。

システム概要図

Formsから記入された情報はPower Automateを経由してOne Drive上のテキストファイルに記入されます。
そのテキストファイルがクラウド環境とローカル環境とで同期し、ローカルに落ちてきます。
そのローカルファイルを読み込んで、C#で組んだデスクトップアプリで表示する、という仕組みです。

実装こまごま

Power Automate

Power Automateは比較的シンプルです。
全体像はこんな感じ。

Formsにコメントが記入され送信されると、このFlowが発火し応答の詳細を取得します。

その後、One drive上にあるテキストファイルを取得して、中身に書き込みます。

One drive上のテキストファイルはこちらからサインインして、トップのディレクトリにあるtest.txtを今回は利用してみます。

text.txtの中身はこんな感じ。
Formsで送信された内容は、このテキストファイルの後ろに「,(応答内容)」という形で追加されていきます。

One Driveの同期

同期の仕方については公式サイトをご覧になってください。
右下にOne Driveのアイコンがあればそれを使って同期。
もしなければOne drive上のテキストファイルはこちらからサインインして、「同期」ボタンをポチーしてみてください。

同期したらローカルのOne Driveフォルダーを確認して、test.txtが来ているか確認してみてください。

C#でデスクトップアプリ構築

今回はVisual Studioを使ってC#でデスクトップアプリを作りました。
・TransparencyKey: Control
・BackColor: Control
・WindowState: Maximized
にすると、背景が透明なデスクトップアプリが全画面で表示されます。
ここにテキストを読み込んだlabelを右から左へ流していくイメージです。

グローバル変数の定義をします。
initial_ballPos:初期のコメントの位置
loc_idx:表示する際のコメント番号
ballSpeed:コメントの流れる速度
stacking_comment_list:来てスタックされているコメント一覧
onFloating_list:流れているlabelの一覧
onWaiting_list:流れておらず画面外で待っているlabelの一覧
となっております。

        //変数宣言
        Point[] initial_ballPos = new Point[10] {
            new Point(1600, 50),
            new Point(1700, 100),
            new Point(1800, 150),
            new Point(1900, 50),
            new Point(2000, 100),
            new Point(2100, 150),
            new Point(2200, 50),
            new Point(2300, 100),
            new Point(2400, 150),
            new Point(2500, 200)
        };

        Random r = new Random();
        int loc_idx = 0;

        //Point[] ballPos = new Point[10];
        Size ballSpeed = new Size(-8,0);
        
        //来たコメントリスト
        Stack<string> stacking_comment_list = new Stack<string>();
        
        //流れているラベル一覧
        List<Label> onFloating_list = new List<Label> {};

        //待っているラベル一覧
        Stack<Label> onWaiting_list = new Stack<Label>();

フォームの読み込みでもろもろ初期化をしつつタイマーをセットします。
comment_timerはコメントをonedriveから取得するタイマーで、3秒おきにキックします。
move_timerはコメントを描画するタイマーで、33ミリ秒おきにキックします。

 public Form1()
        {
            InitializeComponent();
    
	    List<Label> label_list = new List<Label> { label1, label2, label3, label4, label5, label6, label7, label8, label9, label10 };
            for (int idx = 0; idx < 10; idx++)
            {
                label_list[idx].Location = initial_ballPos[idx];
                onWaiting_list.Push(label_list[idx]);
            }

            stacking_comment_list.Push("テストでーす");
           
            Timer comment_timer = new Timer();
            comment_timer.Interval = 3000;
            comment_timer.Tick += new EventHandler(GetComment);
            comment_timer.Start();

            Timer move_timer = new Timer();
            move_timer.Interval = 33;
            move_timer.Tick += new EventHandler(Update);
            move_timer.Start();
        }

3秒おきにキックされるGetCommentメソッドはこちら。
テキストを読み込みカンマで区切ってコメントを取得しstacking_comment_listに格納します。
読み込んだのちに新しいテキストを作成し上書きすることで、test.txtをリフレッシュします。

private void GetComment(object sender, EventArgs e)
        {
            string[] return_list = new string[11];
            string combined_path = Path.Combine(ここにonedriveのパスを入れる, "test.txt");
            List<string> additional_comment_list = new List<string>();

            // 読み込みたいCSVファイルのパスを指定して開く
            StreamReader sr = new StreamReader(combined_path);
            {
                // 末尾まで繰り返す
                while (!sr.EndOfStream)
                {
                    // CSVファイルの一行を読み込む
                    string line = sr.ReadLine();
                    // 読み込んだ一行をカンマ毎に分けて配列に格納する
                    return_list = line.Split(',');
                    additional_comment_list.AddRange(return_list);
                }
            }
            sr.Dispose();

            Encoding sjisEnc = Encoding.GetEncoding("Shift_JIS");
            StreamWriter writer = new StreamWriter(combined_path, false, sjisEnc);
            writer.WriteLine("コメント一覧,");
            writer.Close();

            for (int idx = 1;idx<additional_comment_list.Count;idx++)
            {
                stacking_comment_list.Push(additional_comment_list[idx]);
            }
        }

33ミリ秒ごとにキックされるUpdateメソッドはこちら。
onFloating_listにあるlabelはLocationを移動させ描画します。
stacking_comment_listにコメントがあるときはそれをonWaiting_listにあるlabelのテキストに挿入し放流します。

        private void Update(object sender, EventArgs e)
        {
            for (int i = 0; i < onFloating_list.Count; i++)
            {
                onFloating_list[i].Location += ballSpeed;

                if (onFloating_list[i].Right <= 0)
                {
                    onWaiting_list.Push(onFloating_list[i]);
                    onFloating_list.RemoveAt(i);
                }
            }

            //コメントが来ているとき
            if (stacking_comment_list.Count != 0) {
                if (onWaiting_list.Count != 0)
                {
                    //待ちラベル一覧から一つ取り出し、コメントを挿入して流れているリストに移す
                    Label next_label = onWaiting_list.Pop();
                    next_label.Text = stacking_comment_list.Pop();

                    loc_idx += r.Next(1, 3);
                    next_label.Location = initial_ballPos[loc_idx%10];
                    onFloating_list.Add(next_label);
                }
            }
            
            // 再描画
            Invalidate();
        }

最後に

タイマーのインターバルについては色々好みがあると思うので、この辺りは試行錯誤ポイント。
あとは現状のコードだと複数長いコメントが来たときに重複して表示されてしまうというリスクもあります。
このあたりは改善ポイントかもしれませんね。

Discussion