Prism PubSubEventを簡単に


GetEventとか長ったらしい。

おもむろに以下のクラスを実装しましょう。

using Prism.Events;
using System;

namespace Hoge
{
    public abstract class StaticPubSubEvent<T> : PubSubEvent where T : StaticPubSubEvent<T>, new()
    {
        private static EventAggregator eventAggregator = new EventAggregator();

        private static readonly T instance = eventAggregator.GetEvent<T>();

        protected StaticPubSubEvent() { }

        public static new void Publish() => eventAggregator.GetEvent<T>().InternalPublish();

        public static new SubscriptionToken Subscribe(Action action) => Subscribe(action, ThreadOption.PublisherThread);

        public static new SubscriptionToken Subscribe(Action action, ThreadOption threadOption) => Subscribe(action, threadOption, false);

        public static new SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive) => Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);

        public static new SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
        {
            var actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);

            EventSubscription subscription;
            switch (threadOption)
            {
                case ThreadOption.PublisherThread:
                    subscription = new EventSubscription(actionReference);
                    break;
                case ThreadOption.BackgroundThread:
                    subscription = new BackgroundEventSubscription(actionReference);
                    break;
                case ThreadOption.UIThread:
                    if (instance.SynchronizationContext == null) throw new InvalidOperationException();
                    subscription = new DispatcherEventSubscription(actionReference, instance.SynchronizationContext);
                    break;
                default:
                    subscription = new EventSubscription(actionReference);
                    break;
            }

            return eventAggregator.GetEvent<T>().InternalSubscribe(subscription);
        }
    }
}


で、イベントクラスを用意

namespace Hoge
{
    public sealed class HogeEvent : StaticPubSubEvent<HogeEvent> { }
}

Subscriber

HogeEvent.Subscribe(() =>
{
}, ThreadOption.UIThread);

Publisher

HogeEvent.Publish();

ぼくは気にしないけど、若干いまいちですね。何がいまいちかわかる人だけ自己責任で使ってくださいw(ヒント:EventAggregator


Enum Radio Button Binding


WPFのMVVMは好きです。他の言語で似非MVVM書いてるとなんだかなーって思う。
が、やっぱりWPFは嫌いです。

はい。EnumをBindingしたいとき。

まずおもむろにEnumの拡張を用意しましょう。このブログでも出てきたと思いますよー。

public static class EnumEx
{
    public static string StringValue(this Enum e)
    {
        var fieldInfo = e.GetType().GetField(e.ToString());
        var attribute = (StringValueAttribute)fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false).FirstOrDefault();
        return attribute == null ? null : attribute.StringValue;
    }
}


次にConverterを2つ用意しましょう。

using System;
using System.Globalization;
using System.Windows.Data;

namespace Hoge
{
    [ValueConversion(typeof(Enum), typeof(bool))]
    public class EnumToRadioButtonConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.Equals(Enum.Parse(value.GetType(), parameter.ToString()));
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(bool)value)
                return Binding.DoNothing;
            return Enum.Parse(targetType, parameter.ToString());
        }
    }
}
using System;
using System.Globalization;
using System.Windows.Data;

namespace Hoge
{
    [ValueConversion(typeof(Enum), typeof(string))]
    public class EnumToStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (Enum.Parse(value.GetType(), parameter.ToString()) as Enum).StringValue();
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(bool)value)
                return Binding.DoNothing;
            return Enum.Parse(targetType, parameter.ToString());
        }
    }
}


んで、enumとViewModel。双方向は省略。INotifyPropertyChangedを実装してくだされ。

namespace Hoge
{
    public enum Gender
    {
        [StringValue("男性")]
        Male,
        [StringValue("女性")]
        Female
    }

    public sealed class HogeViewModel
    {
        public Gender Gender { get; set; }
    }
}


で、View

<Page x:Class="Hoge.SamplePage"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Hoge"
            mc:Ignorable="d" 
            d:DesignHeight="500" d:DesignWidth="300">
    <Page.DataContext>
        <local:HogeViewModel x:Name="viewModel" />
    </Page.DataContext>
    <Page.Resources>
        <local:EnumToRadioButtonConverter x:Key="EnumToRadioButton"/>
        <local:EnumToStringConverter x:Key="EnumToString"/>
    </Page.Resources>
    <Grid>
        <StackPanel Orientation="Horizontal">
            <RadioButton GroupName="Gender"
                 Content="{Binding Gender, ConverterParameter=Male, Converter={StaticResource EnumToString}}"
                 IsChecked="{Binding Gender, ConverterParameter=Male, Converter={StaticResource EnumToRadioButton}}"/>
            <RadioButton GroupName="Gender"
                 Content="{Binding Gender, ConverterParameter=Female, Converter={StaticResource EnumToString}}"
                 IsChecked="{Binding Gender, ConverterParameter=Female, Converter={StaticResource EnumToRadioButton}}"/>
        </StackPanel>
    </Grid>
</base:BasePage>

Windowsタスクトレイ常駐型のアプリを作る(WPF版)


よく作るんですけどね。忘れるよね。
なお、ググるキーワードはNotifyIcon。

準備

icoファイルを用意しましょう。
ここにあげようと思ったら、HatenaBlogさんはicoファイルに対応してないようなので皆さん頑張って自力で用意しましょう。

icoファイルをプロジェクトに投入

Resourcesフォルダを作って、ドラッグ&ドロップで投入しましょう。

f:id:devdevdev:20180502212145p:plain

Component作成

Componentsフォルダを作って、右クリック→「追加」→「新しい項目」
コンポーネントクラス」を探し出して、お好きな名前をつけて追加しましょう。
ここでは「NotifyIcon」としました。
f:id:devdevdev:20180502212706p:plain
f:id:devdevdev:20180502212745p:plain

NotifyIconクラスの編集

ツールボックスから「ContextMenuStrip」と「NotifyIcon」を追加します。
ツールボックスで検索して、ドラッグ&ドロップで持っていきましょう。
デフォルトの名前が嫌なので変えましたよ。

f:id:devdevdev:20180502213314p:plainf:id:devdevdev:20180502213319p:plain
f:id:devdevdev:20180502213354p:plain



contextMenuStripの設定

contextMenuStripを右クリックで「項目の編集」

f:id:devdevdev:20180502213605p:plain

MenuItemを追加
「デザイン」→「(Name)」と「表示」→「Text」を編集
Nameはご自由に。Textはタスクトレイのアイコンを右クリックしたときに出るメニューに表示されますよ。

f:id:devdevdev:20180502213811p:plain
f:id:devdevdev:20180502213933p:plain

myNotifyIconの編集

プロパティを開いて、ContextMenuStripとIconを設定

f:id:devdevdev:20180502214154p:plain



NotifyIcon.csの編集

終了メニュークリックしたらアプリケーションを終了。

using System;
using System.ComponentModel;
using System.Windows;

namespace Hoge.Components
{
    public partial class NotifyIcon : Component
    {
        public NotifyIcon()
        {
            InitializeComponent();
            toolStripMenuItemExitApp.Click += delegate (object sender, EventArgs e)
            {
                Application.Current.Shutdown();
            };
        }

        public NotifyIcon(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }
    }
}



App.xamlの編集

App.xaml.csのほうから

using Hoge.Components;
using System.Windows;

namespace Hoge
{
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
        private NotifyIcon notifyIcon = new NotifyIcon();

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            ShutdownMode = ShutdownMode.OnExplicitShutdown;
        }

        protected override void OnExit(ExitEventArgs e)
        {
            base.OnExit(e);
            notifyIcon.Dispose();
        }
    }
}

んで、App.xaml
StartupUri="MainWindow.xaml"って記載があるので削除しましょう。

<Application x:Class="Questionnaire.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Hoge">
    <Application.Resources>
         
    </Application.Resources>
</Application>

参照追加

「System.Drawing」への参照を追加しましょう。疲れたので詳細は説明しません。Windowsエンジニアならわかるでしょ!

いじょ。


Windowsインストーラーの作成


2018年最新版!
Visual Studio2017向け!ぼくはVisual Studio Community 2017だよ!
既存のソリューションにインストーラープロジェクトを追加するよ!最近C#ばっかだよ!

インストール

marketplace.visualstudio.com

こちらからダウンロードしてインストール。

作成

Visual Studioを起動。インストーラーを追加したいソリューションを開いてね!

f:id:devdevdev:20180425100227p:plain
ソリューションを右クリックで「追加」→「新しいプロジェクト」

f:id:devdevdev:20180425100644p:plain
「インストール済み」→「その他のプロジェクトの種類」→「Setup Project」で名前をつけて「OK」



インストールするファイルの指定

f:id:devdevdev:20180425101138p:plain
追加されたプロジェクトを右クリックで「View」→「ファイルシステム
ちなみに「インストール」「アンインストール」からインストーラーとあんインストーラーが起動できるよ!

f:id:devdevdev:20180425101430p:plain
f:id:devdevdev:20180425101708p:plain
単純なものだったら「Application Folder」を右クリック→「Add」→「プロジェクト出力」でexeとかDLLが追加できるよ!
よその記事見るとインストーラービルド時に構成を切り替えてーとかやってたけど、インストーラーなんてここでRelease Any CPUにしとけばいいと思うよ!

インストーラー設定

必要最低限。セットアッププロジェクトのプロパティね。

f:id:devdevdev:20180425102053p:plain

Author→お名前を書きましょう
Manufacturer・ProductName→C:¥Program Files¥[Manufacturer]¥[ProductName]にデフォルトでインストールされるよ!
TargetPlatform→ちゃんと合わせようね!

インストーラー作成

f:id:devdevdev:20180425103046p:plain
ビルド・リビルドしましょう。
フォルダのどっかにexeとmsiができます。


Docker環境構築(Rails)②

devdevdev.hatenablog.com
基礎編はこちら

前回作ったコンテナ上にrailsの実行環境を作っていきますよー
ホスト側は一切汚しません。

とりあえずね
$ docker-compose run app bundle init

app/Gemfileが生成されるよ

Gemfileを編集

デフォルトはこう

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"

最後の行を以下のように書き換えます

gem 'rails', '5.1.4'



docker-compose.ymlの編集

appの下に以下を追記

    environment:
      RAILS_ENV: development

app / volumesの下に以下を追記

      - bundle:/usr/local/bundle

appの一番下に以下を追記

    command: /bin/sh -c 'mkdir -p tmp/sockets/ && bundle exec puma -C config/puma.rb'

一番下に以下を追記

volumes:
  bundle:



Dockerfileの編集

docker/app/Dockerfileに以下を追記

RUN apt-get update && apt-get install -y nodejs

ADD ./app/Gemfile* $APP_ROOT/
RUN bundle install

ビルド

$ docker-compose build



railsアプリケーション作成

appにsshではいる

$ docker-compose run app bash

普通に作る

$ rails new . -d mysql

Gemfileを上書きしていいか聞かれるので「Y」

app/config/database.yml書き換え

development:
  password: password

app/config/puma.rbに以下を追記

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

ビルドして起動

$ docker-compose build
$ docker-compose up

http://localhost/にアクセスしてrailsの画面が表示されればおk。






Docker環境構築(Rails)①

入門~インストールはこちら
devdevdev.hatenablog.com

DockerとDocker-Compose

Dockerはコンテナの管理
Dockerfileで色々管理

Docker-Composeは複数のコンテナの管理
docker-compose.ymlで色々管理
Docker-Composeで環境を作るコンテナの内容によってはDockerfileは不要

くらいに覚えておけば十分かと。

構築する環境

App(Ruby 2.5.1) - アプリケーションサーバ
MySQL 5.7.21 - DB
Nginx 1.13.10 - Webサーバー
phpMyAdmin - 何かと便利
Redis 3.2.11 - KVS



ファイル構成

- app/
- docker-compose.yml
- docker/
    - app/
        - Dockerfile
    - mysql/
        - conf.d/
            - custom.cnf
    - nginx/
        - app.conf

docker/配下のファイルの用意

App

Dockerfileを用意

FROM ruby:2.5.1

ENV APP_ROOT /var/www/app
WORKDIR $APP_ROOT
MySQL

Dockerfileは不要。
mysql/conf.d/custom.cnfを用意しましょう。

[client]
default-character-set=utf8

[mysqld]
character-set-server=utf8

[mysqld_safe]
timezone = UTC
Nginx

Dockerfileは不要
nginx/app.confを用意しましょう。

server {
    listen 80 default_server;
    index index.php index.html index.htm;
    root /var/www/app;
}
phpMyAdmin

Dockerfileも何もいりません。

Redis

Dockerfileも何もいりません。



docker-compose.ymlの作成

version: '2'
services:
  app:
    build:
      context: .
      dockerfile: ./docker/app/Dockerfile
    volumes:
      - ./app:/var/www/app
    ports:
      - 3000:3000
    depends_on:
      - mysql
      - redis
  mysql:
    image: mysql:5.7.21
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: app
    ports:
      - 3306:3306
    volumes:
      - ./docker/mysql:/etc/mysql/conf.d
  nginx:
    image: nginx:1.13.10
    ports:
      - 80:80
    volumes_from:
      - app
    volumes:
      - ./app:/var/www/app
      - ./docker/nginx:/etc/nginx/conf.d
    depends_on:
      - app
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=password
    links:
      - mysql
    ports:
       - 8080:80
  redis:
    image: redis:3.2.11
起動してみる
$ docker-compose up

・エラーが表示されない
http://localhost/にアクセスしてNginxの404が表示される
http://localhost:8080/にアクセスしてphpMyAdminが表示される
のが確認できれば完了。停止はControl + cで。
アプリケーションの用意は次回やります。



Trouble shooting
ERROR: for mysql  Cannot start service mysql: b'Mounts denied: ~

ファイルを作った場所によってはこんなエラーが出る場合があります。

f:id:devdevdev:20180404003130p:plain
右上のクジラアイコンをクリックして、「Preferences」

f:id:devdevdev:20180404003143p:plain
「File Sharing」でdocker-compose.ymlがある場所を追加しましょう。
終わったら「Apply & Restart」してしばらく待ちましょう。






Docker入門~インストール

そういえば書いてなかった気がする&人に説明するので、筆を取ることにしました。

入門

Dockerとは

コンテナ仮想化ツール
簡単に言うとPC上にWebサーバーとか、DBサーバーとかを一つにまとめた環境をポンと構築できるよってやつ。
Vagrantとかは1台の仮想マシンに1マシンになるので、WebサーバーとDBサーバーを立てようと思ったら2つ必要になるのが大きな違いかな。

Dockerを使うメリット

本番環境と同じ環境を簡単に用意できる。
なんならそのまま本番環境に使えるらしい。
起動とか処理が早い。
軽い。
コマンドを叩くだけで環境構築完了!

Docker用語

・イメージ:コンテナの元となるもの。EC2でいうAMI。Docker Hubというところにある。
・コンテナ:イメージを元に作られたもの。ここに各プロジェクト用の情報などが設定される。EC2でいうインスタンス



PC環境

Mac OSX High Sierraです。



インストール

Docker Store
から「Docker Community Edition for Mac」をダウンロードしましょう。
f:id:devdevdev:20180403212400p:plain
f:id:devdevdev:20180403212446p:plain

ダウンロードした「Docker.dmg」をダブルクリックして開きましょう。
書いてあるとおりにドラッグ&ドロップすれば完了!
f:id:devdevdev:20180403215505p:plain

設定

特にないけど。
Launch PadからでもなんでもDockerを起動。

f:id:devdevdev:20180403215718p:plain
「Next」

f:id:devdevdev:20180403215738p:plain
「OK」

f:id:devdevdev:20180403215833p:plain
Macのパスワードを入力して「OK」

f:id:devdevdev:20180403215912p:plain
こうなったら起動完了!