C#やモバイルアプリ関係のログ

C#やXamarin、スマートフォンアプリのことを書いておきたいと思います

c# Windows_Forms 画面をキャプチャ 画面キャプチャにスタンプを押す

C#
Windows Forms
サンプル
・ウィンドウ透過で枠を表現
・画面キャプチャ取得 表示
・画像にスタンプを押す

f:id:Jirobe_Katori:20201220131851p:plain

動作イメージ
f:id:Jirobe_Katori:20201220133055g:plain

github.com

MainForm.cs

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsScreenCap
{
    /// <summary>
    /// 画面キャプチャにスタンプを押すアプリサンプル
    /// 
    /// スタンプのアイコンは下記からお借りした
    /// https://icooon-mono.com/
    /// </summary>
    public partial class MainForm : Form
    {
        private Bitmap stamp = null;
        private (int x, int y) offset;

        public MainForm()
        {
            InitializeComponent();

            //ウィンドウを透明に
            BackColor = Color.Red;
            TransparencyKey = BackColor;
        }

        /// <summary>
        /// 画面をキャプチャ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ScreenCap_Click(object sender, EventArgs e)
        {
            Cursor = Cursors.Arrow;
            stamp = null;

            var screenBounds = Screen.PrimaryScreen.Bounds;
            Bitmap bmp = new Bitmap(screenBounds.Width, screenBounds.Height);

            using (Graphics graphics = Graphics.FromImage(bmp))
                graphics.CopyFromScreen(new Point(Location.X, Location.Y), new Point(-9, -31), bmp.Size);

            capImage.Image = bmp;
        }

        /// <summary>
        /// キャプチャ画面をクリックしてスタンプ貼り付け
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CapImage_Click(object sender, EventArgs e)
        {
            if(stamp != null)
            {
                MouseEventArgs mouseEvent = (MouseEventArgs)e;

                Bitmap bmp = (Bitmap)capImage.Image;

                using (Graphics graphics = Graphics.FromImage(bmp))
                    graphics.DrawImage(stamp, mouseEvent.X - offset.x, mouseEvent.Y - offset.y, stamp.Width, stamp.Height);

                capImage.Image = bmp;
            }
        }

        /// <summary>
        /// スタンプボタン押下時の処理
        /// スタンプの設定とマウスカーソルの変更
        /// </summary>
        /// <param name="icon"></param>
        /// <param name="offset_x"></param>
        /// <param name="offset_y"></param>
        private void Button_Click(Bitmap icon, int offset_x = 9, int offset_y = 9)
        {
            stamp = icon;
            Cursor = new Cursor(icon.GetHicon());
            offset = (offset_x, offset_y);
        }

        private void Button1_Click(object sender, EventArgs e) => Button_Click(Properties.Resources.check_icon);

        private void Button2_Click(object sender, EventArgs e) => Button_Click(Properties.Resources.ng_icon);


    }
}

c# Windows_Forms 背景が透明なウィンドウ

C#
Windows Forms
透明なウィンドウ
f:id:Jirobe_Katori:20201220020134p:plain

github.com

方法①

Form1.cs

using System.Drawing;
using System.Windows.Forms;

namespace TransparentWindowSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            //ウィンドウを透明に Start
            //ここの色は何でもよい
            BackColor = Color.Red;
            TransparencyKey = BackColor;
            //ウィンドウを透明に End
        }
    }
}

方法②

Formクラスの継承した実装にOnPaint(PaintEventArgs e)メソッドを用意
Form1.cs

using System.Drawing;
using System.Windows.Forms;

namespace TransparentWindowSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //ウィンドウを透明に Start
        protected override void OnPaint(PaintEventArgs e)
        {
            //ここの色は何でもよい
            TransparencyKey = Color.Red;
            e.Graphics.FillRectangle(new SolidBrush(TransparencyKey), ClientRectangle);

            base.OnPaint(e);
        }
        //ウィンドウを透明に End
    }
}

参考文献

docs.microsoft.com qiita.com

Androidアプリの公開でOAuthクライアントに悩んだ[Visual Studio 2019][Xamarin.Forms]

まあ、備忘録

目的

Visual Studio 2019からXamarin.FormsのAndroidアプリをGoogle Playに公開したい

内容

わからないので質問サイトを使ったりもしました。ありがたや
[Xamarin]Visual Studio 2019からのAndroidアプリの公開方法

とりあえず、Visual Stuidoでアーカイブ化してGoogle Play公開のためにここまで来たんです。

f:id:Jirobe_Katori:20201018160145p:plain

よくわからんので、下記サイトをみてOAuthクライアントを作ろうと思ったのです。

GooglePlay に公開する

そしたら、Google Play ConsoleのOAuthクライアント欄に「作成」っぽいのがない・・・

f:id:Jirobe_Katori:20201018160525p:plain

仕方がないので「APIとサービス」から直接OAuthアカウントを作ろうと思ったわけです。
ここで詰まりました。

f:id:Jirobe_Katori:20201018160808p:plain

「認証情報」からクライアントIDを用意するんですが、
下記は間違った例です。

f:id:Jirobe_Katori:20201018160947p:plain

Visual Studio(デスクトップアプリ)がGoogleに接続したいのでここでは「デスクトップアプリ」のクライアントIDが必要だったようです。図のようにAndroid用のクライアントIDを作ってもVisual Studioが利用できないわけで・・・

それで、 「デスクトップアプリ」のクライアントIDとクライアントシークレットを得たら、Visual Studioに戻ったわけですよ。早速公開しようとしました。

そうしたら下記のエラーメッセージ

Google Playでは、このアプリの最初のパッケージ(APK/AAB)を手動でアプロードする必要があります。

えー、本当かよ。初回はVisual StudioGoogle Playとの連携はダメなのかよ・・・

仕方がないので、apkファイルを作成して、Google Play Consoleにアップロードしました。
⇒pepk.jarを使って署名をzipファイルにして(?)公開する方法を選択

[todo]今、審査中なので、審査終わったら今度こそ成功するか試す。
[追記]2回目はVisual Studioからアップロードできました

あと、再度Google Play Console見たら、現在は「新しいOAuthクライアントを作成」があると・・・ f:id:Jirobe_Katori:20201018161736p:plain

なんとなく、APIとサービスの「OAuth同意画面」でガチャガチャしたからな気がします。
正直、よくわかりませんでした。

f:id:Jirobe_Katori:20201018160808p:plain

以上
備忘録として記録

Xamarin.Formsのライフサイクルで画面の処理を行いたい

Xamarin.Formsのライフサイクルについて

docs.microsoft.com

画面ごとに画面開始時OnAppearingと画面終了時OnDisappearingはあるけれども

①アプリをフォアグラウンドにしたとき
②バックグラウンドにしたとき

はアプリ全体で一つのイベントとして扱われているわけで、どうしたらいいかよくわかりませんでした。

とりあえず、下記のようにしています。

下記、サンプルソースを元に記述します。

github.com

App.cs

    public partial class App : Application
    {
        public static Action OnStartAction;
        public static Action OnSleepAction;
        public static Action OnResumeAction;

        public App()
        {
            InitializeComponent();

            MainPage = new MainPage();
        }

        protected override void OnStart()
        {
            OnStartAction?.Invoke();
        }

        protected override void OnSleep()
        {
            OnSleepAction?.Invoke();
        }

        protected override void OnResume()
        {
            OnResumeAction?.Invoke();
        }
    }

画面はこう

MainPage.xaml.cs

protected override void OnAppearing()
{
    base.OnAppearing();

    //OnResume()
    App.OnResumeAction = () => DisplayAlert("メッセージ", "OnResume()", "OK");
}

protected override void OnDisappearing()
{
    base.OnDisappearing();

    App.OnResumeAction = null;
}

これでアプリがフォアグラウンドになったときメッセージダイアログを出すようにしています。

Xamarin.Forms MessagingCenterの利用

身内向け
下記、サンプルソースを元に記述します。

github.com

MVVMのViewとViewModelやModelの結合度を低くするために利用するもののようです。変に相互参照が増えすぎるのも汚いのでそういうものだと思っています。

MainPage.xaml.cs

        public MainPage()
        {
            InitializeComponent();

            //ここでViewはViewModelを参照します
            BindingContext = new MainPageViewModel();
        }

上のようにViewはViewModelを参照しています。

MainPageViewModel.cs

        public ICommand DependencyServiceCommand => new Command(() =>
        {
            var openUriService = DependencyService.Get<IOpenUriService>();

            openUriService.OpenUri(URIText);
        });

ボタンを押したときはViewModelがイベントをキャッチしています。

そして、画面遷移やダイアログを出せるのはContentPageを継承しているViewです。

ということで、「ボタンを押したときに画面遷移やダイアログを出すときどうするのか?」という点を解決するためにViewModelからViewへ通知を行う必要が出てきます。

そこで利用するのはMessagingCenterとなります。

画面遷移

MainPage.xaml.cs

        protected override void OnAppearing()
        {
            base.OnAppearing();

            //イベントのセット
            MessagingCenter.Subscribe<MainPageViewModel>(this, "MovePage1",
                async (_) =>
                {
                    await Navigation.PushModalAsync(new Page1(), true);
                });
        }

画面起動時のOnAppearingの時にMessagingCenter.Subscribeでイベントをセットします。ジェネリックに与えるのは送信元のクラスとなります。

MainPageViewModel.cs

public ICommand MovePage1 => new Command(() => MessagingCenter.Send(this,"MovePage1"));

そして、ボタンを押されたときにメッセージを送信します。

メッセージ送信時にパラメータも渡す

パラメータの受け渡しもできます。

Page1.xaml.cs

//タプルにして複数の個の値を渡す
MessagingCenter.Subscribe<Page1ViewModel, (string, string)>(this, "ShowMessage",
(_, t) =>
{
    //メッセージダイアログ
    DisplayAlert(t.Item1, t.Item2, "OK");
});

少しかみ砕きます。<Page1ViewModel, (string, string)> と (, t) の関係ですが、Page1ViewModelがで (string, string)がtとしています。(string, string)の一つ目がt.Item1で二つ目がt.Item2です。

Page1ViewModel.cs

public ICommand MessageCommand => new Command(() => MessagingCenter.Send(this, "ShowMessage", ("Message", MessageText ?? "Empty.")));

これはテキストボックスに入力されたテキストをダイアログに出すサンプルです。(string,string)を必要に応じて変えてください。

MessagingCenter.Subscribeについて

MessagingCenter.Subscribeはイベントハンドラの+=と同じです。

試しに下記のように書き換えてみてください。同じものを二回実行するようにしてみます。

Page1.xaml.cs

//タプルにして複数の個の値を渡す
MessagingCenter.Subscribe<Page1ViewModel, (string, string)>(this, "ShowMessage",
(_, t) =>
{
    //メッセージダイアログ
    DisplayAlert(t.Item1, t.Item2, "OK");
});

MessagingCenter.Subscribe<Page1ViewModel, (string, string)>(this, "ShowMessage",
(_, t) =>
{
    //メッセージダイアログ
    DisplayAlert(t.Item1, t.Item2, "OK");
});

これで実行するとダイアログが2回表示されるはずです。適切にイベントを破棄(MessagingCenter.Unsubscribe)しないとバグのもととなります。

ADBの使い方

AndroidLinuxです。Linuxとしてコマンドラインで接続できます。

Android ADB コマンドプロンプト

実機をPCに接続するかAndroidVMを起動してください。

VMの起動方法は
VIsual Studio 2017 → ツール →AndroidAndroidバイスマネージャ
となります。適当なものを開始押下で開始してください。

Androidの用意ができましたら、実際にADBを利用するためにコマンドプロンプトを起動します。
VIsual Studio 2017 → ツール →AndroidAndroid ADB コマンドプロンプト

コマンドプロンプトに下記を入力しましょう。

adb shell

すると下記のように白枠のところがデバイス名に代わると思います。それで接続成功です。

f:id:Jirobe_Katori:20190330115214p:plain

ls、cdなどLinuxのコマンドを売ってみましょう。

ADBはどういうときに使うのか logcat

接続した状態でlogcatと打ってみましょう。

logcat

たくさん文字が出てきたと思います。これはOSのシステムのログのようなもので、アプリはここにメッセージを吐いたりできます。OSの機能などはここにログを書くことが多いのでデバッグの際にはよく使います。

ADBはどういうときに使うのか run-as

AndroidのアプリはLinuxでいうパッケージです。パッケージにはローカルエリアがあり、そこにキャッシュやファイルを持っておきます。そのエリアは保護されており、アプリ以外のプロセスはアクセスできません。その保護領域にファイルなどを配置しておくと安全ですが、他プロセスがアクセスできないので確認もできません。そういう時にこのADBを利用すればアクセスできます。

対象のアプリがdebugビルドの場合はADBで保護されたアプリのローカルエリアにアクセスできます。

releaseビルドではAndroidManifest.xmlのapplicationタグに「android:debuggable="true"」 を指定する必要があります。

実際にやってみましょう。Xamarin.Formsのプロジェクトを例にとります。

一度、デバッグビルドで実行しておきます。
f:id:Jirobe_Katori:20190330120232p:plain

これでアプリがAndroidにデプロイされました。次にアプリのパッケージ名を確認します。

Androidのプロジェクトのプロパティを開きましょう。
f:id:Jirobe_Katori:20190330120426p:plain

そうしたらAndroidマニュフェストにカーソルを合わせてください。
f:id:Jirobe_Katori:20190330120506p:plain

パッケージ名が表示されます。このパッケージ名をADB接続後の状態で利用します。

run-as パッケージ名

すると左側がパッケージ名になると成功です。 f:id:Jirobe_Katori:20190330120752p:plain

ここのfilesがC#の特殊ディレクトリに当たります。※DependencyService不要でライブラリに書ける内容です。

string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

共有エリアにファイルを置いたり読んだりはユーザーの許可が必要だったり大変です。共有エリアに置く必要がなければ上記の場所を利用しましょう。

C# モダンな書き方で引っ掛かりそうなところのまとめ

ほかの言語も同じですが、C#にはバージョンがあります。現在のC#のバージョンはVisual Studio 2017でC#7。Visual Studio 2019でC#8になるでしょうか。

C#のソースを読んでいる見慣れない構文が出てくるかと思います。バージョンが上がって知らない構文が増えているわけです。

Xamarinのサンプルソース読むにも出てくるので本記事でそういった人向けに直感的にわからなさそうなものを列挙したいと思います。

列挙は独断と偏見で選んでいます。本記事になくて、検索しようにも構文がわからないとか検索キーワードすらわからないかと思います。

そういうときは↓のサイトが非常に素晴らしいです。 ufcpp.net C#の最新のバージョンから新機能を見てけばいずれ疑問は解消するでしょう。

ラムダ式

そこまで(多分)新しいわけでもなく、また非常に便利です。delegateやeventhandlerというものを使ったことがない人にはなじみがない概念かもしれません。

自作サンプルです。
github.com

関数を変数として扱えます。そして、ラムダ式は変数に関数を代入する際の関数の書き方の一つであると思ってください。推論で引数の型(intとかstring)を省略できます。引数を囲む()も省略できます。

クラス内の関数定義で1行で書ききれる内容なら関数の定義にもラムダ式を利用できます。getter/setterにも利用できます。

    class Program
    {
        public string Age(int age) => age + "歳";
    }

とりあえず、なんとなく慣れてください。

Action Func

関数を変数として扱えます。パラメータの代入で右側がラムダ式とすると、左側のパラメータの型がActionやFuncとなります。

string hoge = "hogehoge!";

//代入
Action<string> a = s => Console.WriteLine($"This is {s}");

//実行
a("Action");

//代入
Func<string> f = () => "This is Func";
//実行
string isThis = f();

//代入
Func<string, string> ff = s => {
s = s + "<string, string>";
return $"This is {s}";
};

ActionとFuncはPLSQLのファンクションとプロシージャのようなものです。戻り値があるかないかの違いです。ジェネリックで引数と戻り値の型の指定がいります。{}で囲むと複数行書けます。

null 条件演算子

オブジェクトが関数を実行する際に?がついていることがあります。

Cat?.Eat("マグロ");

これは下記のようなものです。

if(Cat != null){
   Cat.Eat("マグロ");
}

タプル

DBでいうところのレコードに当たる概念です。

限定的な利用の変数の入れ物のクラスを一々定義しなくてもいいものになります。便利です。

class Program
{
    static void Main(string[] args)
    {
        var (name, age) = GetMe();
        Console.WriteLine($"{name} {age}歳");

        //取得不要な場合は_としてください
        var (name2, _) = GetMe();
        Console.WriteLine($"{name2}歳");

        Console.Read();
    }

    static (string name , int age) GetMe()
    {
        string name = "ちばらき 太郎";
        int age = 31;

        return (name, age);
    }
}

ローカル関数

関数の中に関数をかけます。関数の中のローカル変数をそのまま使えて便利です。

static void Main(string[] args)
{
    int age = 87;

    void WriteLine(string s)
    {
        Console.WriteLine($"{age}歳 {s}");
    }

    switch (age)
    {
        case int i when i <= 20:
            {
                WriteLine("若い!");
                break;
            }
        case int i when i > 20:
            {
                WriteLine("若くない!");
                break;
            }
    }

    Console.Read();
}