20190527のC#に関する記事は4件です。

C#のWindowsFormアプリケーションでコンソールも表示させる(NLogのログを表示させたい)

WindowsFormアプリケーションではNLogのconsoleTargetをしてもログが出ない

C#をまじめに使い始めて10日程度のド素人です。
やっている案件はAccess2016+VBAで突貫で造った膨大なデータベース処理をごっそりC#+MySQLに移植すること。
そして、処理速度、ユーザーへのアプリ配布の円滑化、開発効率、セキュリティの全てを改善すること。

VBAではデバッグ表示にテキストファイルにオレオレロガーで生成していましたが、1ファイル20MB以上ある大きなCSVを何個も読み込んであれこれしているため、CSV以上にでかいテキストファイルからトレースするのは大変でした。

C#はIDEで簡単なトレースできますが、リリース時はLogファイルにして、普段はコンソールに出たら便利だなあとNLogを使うことに。
以下、コンソールアプリケーションでないのにコンソールを出してロギングするのに意外とはまったので備忘録メモ

NLogを設定する

NuGetでNLogを入れます。
たくさん情報あるので、configをソースで行う場合の部分のみ。

何気に、多重起動防止、ログインフォームをさくっと実装するパターンに改変しています。

Program.CSでNLog設定

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using MySql.Data.MySqlClient;
using NLog;
using NLog.Config;
using NLog.Targets;

namespace <名前空間>
{
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            // 1.configuration を生成 
            var config = new LoggingConfiguration();

            // 2.targetを生成し configurationに設定 
            var consoleTarget = new ColoredConsoleTarget();
            config.AddTarget("console", consoleTarget);

            var fileTarget = new FileTarget();
            config.AddTarget("file", fileTarget);

            // 3.targetのプロパティを設定
            consoleTarget.Layout = @"${date:format=HH\\:MM\\:ss} ${logger} ${message}";
            fileTarget.FileName = "${basedir}/file.txt";
            fileTarget.Layout = "${message}";

            // 4.規則を定義
            var rule1 = new LoggingRule("*", LogLevel.Trace, consoleTarget);
            config.LoggingRules.Add(rule1);

            var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget);
            config.LoggingRules.Add(rule2);

            // 5.構成を有効化
            LogManager.Configuration = config;

            // 6._log.*でログ生成し放題にする
            Logger _log = LogManager.GetCurrentClassLogger();

            // ログのサンプル
            _log.Trace("Sample trace message");
            _log.Debug("Sample debug message");
            _log.Info("Sample informational message");
            _log.Warn("Sample warning message");
            _log.Error("Sample error message");
            _log.Fatal("Sample fatal error message");

            // これを入れると、コンソールでキーを押すまでプログラム一時停止
            Console.ReadKey();

            // ミューテックス生成
            _log.Info("mutex生成");
            using (System.Threading.Mutex mutex = new System.Threading.Mutex(false, Application.ProductName))
            {

                // 二重起動を禁止する
                if (mutex.WaitOne(0, false))
                {
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);

                    //ログインウィンドウ表示
                    _log.Trace("FormLogin.ShowDialog");
                    FormLogin frm = new FormLogin();
                    if (System.Windows.Forms.DialogResult.OK == frm.ShowDialog())
                    {
                        //メインウィンドウを表示
                        Application.Run(new FormMain());
                    }
                }
                else
                {
                    _log.Trace("多重起動エラー");
                    MessageBox.Show("多重起動できません。");
                }

            }            



        }
    }
}



上記のProgram.CSではコンソールは出ません

コンソールが出ないので期待した動作結果になりません
具体的には下記のコードの何が悪いかと悩む羽目になります

config.AddTarget("console", consoleTarget);

デバック時のみコンソールを出したい

粗削りですが下記で起動時にコンソールが出るようになります。

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using MySql.Data.MySqlClient;
using NLog;
using NLog.Config;
using NLog.Targets;
using System.Runtime.InteropServices; //DllImport属性を使用するために必要

namespace <名前空間>
{
    static class Program
    {
        #if DEBUG
            //デバック時のみコンソールアプリを呼び出す
            [DllImport("kernel32.dll")]
            static extern bool AllocConsole();
        #endif

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            //コンソールアプリ表示(Nlogのコンソールへのログ出力が見えるようになります)
            //AllocConsole();以後は、
            //Console.ReadKey(); とかけば停止ができる。デバックで便利。キー押せば再開する。
            AllocConsole();

//以下略

まとめ

色々書きましたが、3時間後、Console.WriteLineでええやんとわかりました。
NLog自体がエラーを吐いたり、デバック出力オプションをいじれば、Console.WriteLineで十分とわかったため。

VisualStudio2019 + C# では、長らく更新されていないNLogに頼るよりも、C#の基本構成でやるほうが、意図せぬエラーに遭遇することもなく、そもそも、理解しながらコーディングできるので早いです。

Console.WriteLine自体をログファイルに書き出しもできるようですし、知っていることをコツコツが最短手のようです。

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

LimitedConcurrencyLevelTaskScheduler

概要
C#タスクの平行実行について記述します。

キーワード:
LimitedConcurrencyLevelTaskScheduler

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

Twitterで自分用TODOツイートしたらTrelloに自動登録するようにしたって話

Twitterで自分用TODOツイートしたらTrelloに自動登録するようにしたって話【C#】

休日はやることリスト作らないと一日中寝て終わってしまうので、TwitterにTODOを書き込むようにしています。

ツイートしたからといって誰が進捗確認してくれるわけでもないのですが「なんかやらねーとなー」感が出るので気に入ってます。

さて、このTODOツイート。自分のツイート見に行かないとそもそも見れないのが手間です。

プライベートのタスクはTerlloを使っているので一元管理したいのですが、Trelloに書いてからツイートするの面倒なんですよね。Trelloにだけ書いた場合、結局誰にも見られてないし後でいいかな~状態になってしまうのがダメ。宣言大事。

ので、自分用のフォーマットでTODOツイートをしたときに、Trelloに自動でカードを追加するプログラムをのんびり自作しました。

TwitterでツイートしたらIFTTTからAuzure Functionsの自作APIへデータ送信して、任意のツイートフォーマットだったらTrelloにカードを登録するという構成です。

完成イメージ

下記のようなツイートをすると

こんな感じにTrelloに追加されます。

Twitterで自分用TODOツイートしたらTrelloに自動で登録するようにしたって話【C#】_1.PNG

API Key、User Tokenの取得

下記ページから開発者向けAPIキーと、ローカルテストで使用できるトークンを生成・取得します。

https://trello.com/1/appKey/generate

今回は自分用のため最後までローカルテスト用のUser Tokenを利用して開発します。この雑な開発が趣味プログラミングの醍醐味です。

Azure FunctionにTrelloのカードを登録する処理を書く

今回はManatee.Trelloを使わせてもらいました。

https://github.com/gregsdennis/Manatee.Trello

TrelloのC#APIラッパー。とりあえず今回は使いたかった機能は全て持ってました。感謝。

とりあえず認証してカードを追加するには下記コードで実現できます。

TrelloAuthorization.Default.AppKey = "[your application key]";
TrelloAuthorization.Default.UserToken = "[your user token]";

var  trelloFactory = new TrelloFactory();
var me = await trelloFactory.Me();
var board = me.Boards.Where(x => x.Name == "TodoBoard").Single();
await board.Refresh();
var list = board.List.Where(x => x.Name == "Progress").Single();
await list.Cards.Add("やること1");

簡単。短い。素敵。ありがとうManatee.Trello

で、実際にTweetのパース、Trelloのカード追加してるコードはこのへん。

https://github.com/kokeiro001/KokeiroLifeLogger/blob/2413860b0e5a29a0fc94d59440acee69465a4cf0/KokeiroLifeLogger/Services/MyTweetService.cs#L30-L156

IFTTTにAppletを登録する

TweetしたらWebRequestするAppletを登録します。

Twitterで自分用TODOツイートしたらTrelloに自動で登録するようにしたって話【C#】_0.PNG

今回はツイート内容(Text)のみを取得しました。

IFTTTは素直にWebRequest作れるのが良いですね。簡単に自作サービスと繋げられます。

まとめ

これまでもIFTTTとTwitterの連携はやってたのですが、Trelloとの連携は初めてでした。

ライブラリのおかげでサクッと実装できました。感謝。

開発者用トークンを使い続けるのも不格好なので、気が向いたら清書したいですね。

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

【.NET】フォームを強制的にアクティブにする

はじめに

工場など使用する場合、PCを単一のアプリケーション専用にすることがあります。スタートアップに専用のアプリケーションで登録して即使用(入力)できるようにしたいわけです。

Windows 10にてスタートアップからアプリケーション起動後にテキストボックスに入力出来るようになっているはずなのですが、入力を受け付けません。
見た目はアクティブでテキストボックス上はキャレットが点滅しているが入力ができないという状態です。ただ、厄介なことに毎回発生するわけではないです。

原因

タスクバーでアイコンが点滅した状態になっており、アプリケーションが正常にアクティブになっていないため。
アプリケーションをマウスクリック等でアクティブにすれば入力ができるようになります。

OS起動後にメニューからやexeのダブルクリックなどでアプリケーションを起動する上では問題が発生しないので、スタートアップ登録による起動からだと何かしらの影響により発生してしまうようです。

暫定対応

exeのプロパティにて「管理者としてこのプログラムを実行する」にチェックを付けると回避できることを同僚が見つけました。

恒久対応

出来るならプログラムで何とか回避したい。
以前の記事「【.NET】ウインドウを一時的に最前面に表示しフォーカスを奪う」で、強制的にアクティブにしたことがあったので、これを採用しました。

フォーム起動時にForceActiveWindow()を呼んで強制的にアクティブにします。

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);

/// <summary>
/// Windowフォームアクティブ化処理
/// </summary>
/// <param name="handle">フォームハンドル</param>
/// <returns>true : 成功 / false : 失敗</returns>
private static bool ForceActive(IntPtr handle)
{
    const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
    const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
    const int SPIF_SENDCHANGE = 0x2;

    IntPtr dummy = IntPtr.Zero;
    IntPtr timeout = IntPtr.Zero;

    bool isSuccess = false;

    int processId;
    // フォアグラウンドウィンドウを作成したスレッドのIDを取得
    int foregroundID = GetWindowThreadProcessId(GetForegroundWindow(), out processId);
    // 目的のウィンドウを作成したスレッドのIDを取得
    int targetID = GetWindowThreadProcessId(handle, out processId);

    // スレッドのインプット状態を結び付ける
    AttachThreadInput(targetID, foregroundID, true);

    // 現在の設定を timeout に保存
    SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, timeout, 0);
    // ウィンドウの切り替え時間を 0ms にする
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, dummy, SPIF_SENDCHANGE);

    // ウィンドウをフォアグラウンドに持ってくる
    isSuccess = SetForegroundWindow(handle);

    // 設定を元に戻す
    SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, timeout, SPIF_SENDCHANGE);

    // スレッドのインプット状態を切り離す
    AttachThreadInput(targetID, foregroundID, false);

    return isSuccess;
}

Windowフォームの場合

WinForm
/// <summary>
/// Windowフォームアクティブ化処理
/// </summary>
private void ForceActiveWindow()
{
    // タスクバーが点滅しフォーカスはあるのに入力できない状態になるため
    for (int i = 0; i < 3; i++)
        if (ForceActive(this.Handle)) break;
}

WPFの場合

WPF
/// <summary>
/// WPFアクティブ化処理
/// </summary>
private void ForceActiveWindow()
{
    var helper = new System.Windows.Interop.WindowInteropHelper(this);
    // タスクバーが点滅しフォーカスはあるのに入力できない状態になるため
    for (int i = 0; i < 3; i++)
        if (ForceActive(helper.Handle)) break;
}

最後に

Windows 7 からWindows 10 に移行する上で問題になりうる一つです。検証する際にチェック対象にしておくといいかも知れませんね。

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