shuntaro3.log

技術ネタとか。

Azure Pipelines 上でテストカバレッジをいい感じに表示する

◆ はじめに

皆様、AzureDevOps使ってますか?旧 VSTS。 カンバンから、リポジトリから、CI/CDから、開発にかかわる便利なサービスが一通りそろってる印象。 今の職場では、その中でもCI/CDサービスであるAzure Pipelinesを活用して開発を行っている。 で、この度そのAzure Pipelines上でテストカバレッジを計測、表示したくなったのでやってみた、という記事。

◆ お手軽版

最も簡単な方法は、Pipeline のタスクの1つである「Visual Studio Test」を使って実現する方法。 タスク内設定の「Code coverage enabled」にチェックをつけるとそれだけで、Pipeline 実行後の Summury 上にカバレッジが表示される。 ただし、外部ライブラリ等を入れている場合は、それを含めたカバレッジになってしまうので、 カバレッジ測定の範囲を指定したrunsettingsファイルを作成してあげる必要がある。

フォルダ構成

- TestApp
- TestApp.Test
    - vstest.runsettings

vstest.runsettings

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
  <!-- Configurations for data collectors -->
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
        <Configuration>
          <CodeCoverage>
            <ModulePaths>
              <Include>
                <ModulePath>.*TestApp.*</ModulePath>
              </Include>
              <Exclude>
                <ModulePath>.*TestApp.Test*</ModulePath>
              </Exclude>
            </ModulePaths>
            <!-- We recommend you do not change the following values: -->
            <UseVerifiableInstrumentation>True</UseVerifiableInstrumentation>
            <AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses>
            <CollectFromChildProcesses>True</CollectFromChildProcesses>
            <CollectAspDotNet>False</CollectAspDotNet>
          </CodeCoverage>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

設定例

f:id:shuntaro3:20190325231021p:plain

結果

f:id:shuntaro3:20190325231024p:plain

ちなみに、この方法は、TFUGのSlackで @kkamegawaさん、@changeworldさん からきいた。 聞いてみればなんてことないが、個人的には「うおーわからんぞー」とか言いながらあれやこれやしてたので、目からうろこで感動した。 素晴らしい。自分も貢献していきたい。slackに参加したい方は以下ブログに記載があるのでどうぞ。 https://tfsug.hatenablog.jp/entry/2019/01/19/222017

◆ OpenCover + ReportGenerator 版

↑のやり方は非常に簡単で良いのだが、カバレッジ結果の詳細表示ができない。 なので、OpenCover + ReportGenerator を使っていい感じに表示させる。

1) テストプロジェクトに OpenCover を Nuget で追加

Azure Repos への push を忘れずに。

github.com

2) Azure Pipelines で以下のようなタスクを設定

f:id:shuntaro3:20190326001458p:plain

また、ビルド設定を「Debug」にする。

f:id:shuntaro3:20190326001511p:plain

以下、タスクの詳細設定。

2-1) Visual Studio Test タスク

今回は「Code coverage enabled」のチェックはつけない。

2-2) Command Line タスク

テストプロジェクトへ入れたOpenCoverをコマンドラインで実行し、カバレッジ結果を出力する。

Script
%OPEN_COVER% -register:user -target:%MSTEST% -targetargs:%TEST_DLL_FILE% -targetdir:%TEST_DLL_DIR% -filter:%FILTER% -output:%OUTPUT_FILE%
Environment Variables
Name Value
MSTEST "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
TEST_DLL_DIR "TestApp.Test\bin\Debug"
TEST_DLL_FILE "TestApp.Test.dll"
FILTER "+[TestApp] -[TestApp.Test]"
OUTPUT_FILE "coverage.xml"
OPEN_COVER ".\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe"

2-3) ReportGenerator タスク

「ReportGenerator」タスクはマーケットプレースから追加する必要がある。

marketplace.visualstudio.com

2-2) で出力したカバレッジ結果の xml を読み込み、カバレッジレポートを出力する。

f:id:shuntaro3:20190326001516p:plain

2-4) Publish Code Coverage Results タスク

2-3) で出力したカバレッジレポートを読み込む。

f:id:shuntaro3:20190326001911p:plain

3) いい感じの結果

Good.

f:id:shuntaro3:20190326002659p:plain

◆ まとめ

Azure Pipelines でコードカバレッジのお手軽表示から、OpenCover + ReportGenerator を用いていい感じの表示方法について書いた。 ちなみに、今回の例は .NET Frameworkです。.NET Core ならもう少し簡単に表示できるっぽい。TsuyoshiUshioさんの素晴らしい記事↓。

medium.com

...時代はやはり .NET Core だな。

JSON.NET で ISO8601 規格の日付時刻文字列を JObject に DateTimeOffset として読み込む

f:id:shuntaro3:20190211012539j:plain

◆ はじめに

今さら JSON.NET かよって感じですが、仕事中にはまったのでメモ。 やりたいことは、JSON 内の ISO8601規格の日付時刻文字列を一旦 JObject に変換して、 変換した JObject から DatetimeOffset の値を取得したい。

◆ 何も考えずにやるとこうなる

素直に実装してみる。

Program.cs

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;

namespace DateTimeOffsetParseSample
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var stream = new StreamReader(@"Json\sample_data.json"))
            {
                var json = stream.ReadToEnd();

                // json -> JObject
                var data = JsonConvert.DeserializeObject<JObject>(json);

                // JObject から DateTimeOffset 取得
                var createdAtUtc = data.Value<DateTimeOffset>("created_at_utc");
                var createdAtJst = data.Value<DateTimeOffset>("created_at_jst");

                Console.WriteLine(createdAtUtc);
                Console.WriteLine(createdAtJst);
            }
            Console.ReadLine();
        }
    }
}

Json\sample_data.json

{
  "created_at_utc": "2019-02-11T04:50:40Z",
  "created_at_jst": "2019-02-11T13:50:40+09:00"
}

実行してみるとわかるが、var createdAtUtc = data.Value<DateTimeOffset>("created_at_utc");の部分で例外が発生する。

System.InvalidCastException
  HResult=0x80004002
  Message='System.DateTime' から 'System.DateTimeOffset' への無効なキャストです。
  Source=mscorlib

なんでや。Value<DateTimeOffset>してるのに。

◆ 解決方法

結論 DeserializeObject の引数で、日付変換設定を渡してあげれば良い。

Program.cs

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;

namespace DateTimeOffsetParseSample
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var stream = new StreamReader(@"Json\sample_data.json"))
            {
                var json = stream.ReadToEnd();

                // json -> JObject
                var settings = new JsonSerializerSettings
                {
                    DateParseHandling = DateParseHandling.DateTimeOffset,
                    DateFormatHandling = DateFormatHandling.IsoDateFormat,
                    DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind
                };
                var data = JsonConvert.DeserializeObject<JObject>(json, settings);

                // JObject から DateTimeOffset 取得
                var createdAtUtc = data.Value<DateTimeOffset>("created_at_utc");
                var createdAtJst = data.Value<DateTimeOffset>("created_at_jst");

                Console.WriteLine(createdAtUtc);
                Console.WriteLine(createdAtJst);
            }
            Console.ReadLine();
        }
    }
}

結果

2019/02/11 4:50:40 +00:00
2019/02/11 13:50:40 +09:00

めでたし、めでたし。

◆ まとめ

こんなことやりたい人がどれだけいるのかはわからないけど、知ってればなんてことない話。 ちなみに、JObject 経由しない場合だと、何も考えずに変換できる。なんでや。 あと、JSON.NET のキャラクターはなんかむかつく。以上。

WPF で ItemsControl 内の子コントロールの変更を親へ通知する

◆ はじめに

ItemsControl にバインドしたリスト要素に応じて、TextBox が生成されるような場面で、TextBox の値が変わったらその都度変更通知を受け取りたい、そんなニッチな要望を満たすためにかなりはまったのでメモ。素の WPF ならどう実現できるか、ライブラリを駆使したらどうなるか、など色々思考錯誤してみた。

◆ 具体的にはどんな画面か

サンプルとして、以下イメージの画面で実装していく。ItemsControl にバインドされたリスト(数値)分だけ TextBox が生成され、その TextBox の合計が一番下の TextBlock に表示される。合計の値は、ItemsControl 内 TextBox の値が変更されるたびに更新されるようにする。

f:id:shuntaro3:20181228175354p:plain

実現方法については、3つの方法で試してみた。

  1. 素の WPF のみ
  2. Prism
  3. Prism + ReactiveProperty

◆ 実現方法

1. 素の WPF のみ

リストに表示するためのデータをクラスとして作成し、そのクラス内のプロパティ変更箇所( Setter )で、親(リストが配置されている ViewModel )に対しての変更通知処理を実行する。ポイントは、以下3点かな。

  • データクラスに INotifyPropertyChanged インターフェースを実装
  • リストは ObservableCollection で定義
  • 親への変更通知は、 Action で実現し、データクラスのコンストラクタで設定

多分この方法が一番簡単だと思います(優しいマサカリ期待)。

Number.cs

using System;
using System.ComponentModel;

namespace WPFSampleTextBoxListNotification.Models
{
    public class Number : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string name)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

        private int _amount;
        public int Amount
        {
            get => _amount;
            set
            {
                _amount = value;
                OnPropertyChanged(nameof(Amount));

                _amountChangedNotification?.Invoke();
            }
        }

        private Action _amountChangedNotification = null;

        public Number(int amount, Action amountChangedNotification = null)
        {
            Amount = amount;
            _amountChangedNotification = amountChangedNotification;
        }
    }
}

NormalWindowViewModel.cs

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using WPFSampleTextBoxListNotification.Models;

namespace WPFSampleTextBoxListNotification.ViewModels
{
    public class NormalWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string name)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

        public string _title = "素のWPF";
        public string Title
        {
            get => _title;
            set
            {
                _title = value;
                OnPropertyChanged(nameof(Title));
            }
        }

        public ObservableCollection<Number> _numbers;
        public ObservableCollection<Number> Numbers
        {
            get => _numbers;
            set
            {
                _numbers = value;
                OnPropertyChanged(nameof(Title));
            }
        }

        public int _total;
        public int Total
        {
            get => Numbers.Sum(x => x.Amount);
        }

        public NormalWindowViewModel()
        {
            var list = new ObservableCollection<Number>()
            {
                new Number(100, () => OnPropertyChanged(nameof(Total))),
                new Number(200, () => OnPropertyChanged(nameof(Total))),
                new Number(300, () => OnPropertyChanged(nameof(Total))),
            };

            Numbers = list;
        }
    }
}

NormalWindow.xaml

<Window
    x:Class="WPFSampleTextBoxListNotification.Views.NormalWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="NormalWindow"
    Width="300"
    Height="400"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Content="素のWPF" />
        <ItemsControl
            Grid.Row="1"
            Margin="5"
            Padding="5"
            ItemsSource="{Binding Numbers}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBox
                        Margin="5"
                        Padding="5"
                        HorizontalContentAlignment="Right"
                        Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <TextBlock
            Grid.Row="2"
            Margin="5"
            Padding="5"
            HorizontalAlignment="Right"
            Text="{Binding Total, Mode=OneWay}" />
    </Grid>
</Window>

2. Prism

次は、 MVVM フレームワークである Prism を導入したパターンで実装してみる。基本、素の WPF と同じものになる。変更通知のメソッドが Prsim が提供する RaisePropertyChanged になっただけ。また、 INotifyPropertyChanged のインターフェースも自動実装されているので、 ViewModel 内の記述量が減った感じ。

3. Prism + ReactiveProperty

最後は、Prism + ReactiveProperty で実装したパターン。ReactiveProperty については、以下を眺めれば何となくわかると思われる。

github.com

okazuki.jp

blog.okazuki.jp

qiita.com

このパターンだと、上述した2つのパターンのようにリスト要素のデータクラス内に通知する仕組みを実装する必要がない。ReactiveProperty が提供する ObserveElementObservableProperty でリスト内要素の変更監視が可能となっている。ただ、リスト要素のデータクラスを Prism の変更通知で実装するとうまく動かなかった。なので、 ReactiveProperty で全て実装している。

http://okazuki.jp/ReactiveProperty/features/Extension-methods/#observe-propertychanged-events-of-observablecollections-items

また、はまりどころとしては、using Sysmte; の記述がないと、Subscribe でエラーとなる。

qiita.com

NumberReactiveProperty.cs

using Prism.Mvvm;
using Reactive.Bindings;

namespace WPFSampleTextBoxListNotification.Models
{
    public class NumberReactiveProperty : BindableBase
    {
        public ReactiveProperty<int> Amount { get; set; } = new ReactiveProperty<int>();

        public NumberReactiveProperty(int amount)
        {
            Amount.Value = amount;
        }
    }
}

ReactivePropertyWindowViewModel.cs

using Prism.Mvvm;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using WPFSampleTextBoxListNotification.Models;

namespace WPFSampleTextBoxListNotification.ViewModels
{
    public class ReactivePropertyWindowViewModel : BindableBase
    {
        public ReactiveProperty<string> Title { get; set; }
            = new ReactiveProperty<string>("Prism + ReactiveProperty");

        public ReactiveCollection<NumberReactiveProperty> Numbers { get; }
            = new ReactiveCollection<NumberReactiveProperty>();

        public ReactiveProperty<int> Total { get; } = new ReactiveProperty<int>();

        public ReactivePropertyWindowViewModel()
        {
            var list = new ObservableCollection<NumberReactiveProperty>()
            {
                new NumberReactiveProperty(100),
                new NumberReactiveProperty(200),
                new NumberReactiveProperty(300),
            };

            Numbers
                .ObserveElementObservableProperty(x => x.Amount)
                .Subscribe(x =>
                {
                    Total.Value = Numbers.Sum(y => y.Amount.Value);
                });
            Numbers.AddRange(list);
        }
    }
}

ReactivePropertyWindow.xaml

<Window
    x:Class="WPFSampleTextBoxListNotification.Views.ReactivePropertyWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:prism="http://prismlibrary.com/"
    Title="{Binding Title.Value}"
    Width="300"
    Height="400"
    prism:ViewModelLocator.AutoWireViewModel="True"
    WindowStartupLocation="CenterScreen">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Content="Prism + ReactiveProperty" />
        <ItemsControl
            Grid.Row="1"
            Margin="5"
            Padding="5"
            ItemsSource="{Binding Numbers}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBox
                        Margin="5"
                        Padding="5"
                        HorizontalContentAlignment="Right"
                        Text="{Binding Amount.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <TextBlock
            Grid.Row="2"
            Margin="5"
            Padding="5"
            HorizontalAlignment="Right"
            Text="{Binding Total.Value, Mode=OneWay}" />
    </Grid>
</Window>

◆ まとめ

なんかできそうなんだけど、デフォルトではできないみたいなニッチさで、調べたり実験したりするのが大変だった。どの実装でもそこそこ綺麗にできたので満足している。ReactiveProperty については、基本便利だけど、細かい部分で動きがつかみ辛く、自分のノウハウ貯めないと仕事では使えないかなーって感じ。サンプルソースは以下。見たい方はどうぞ。

github.com

WPF で子要素を持つ UserControl を作る

◆ はじめに

現在のプロジェクト案件で WPF を使っておりまして、レビューやらで Xaml をみる機会が増えてきました。その Xaml 内に同じような記述が繰り返しでてきたりすると、共通化や部品化したくなるのが人の性...。せっせこ部品化していたときに、子要素を持つコントロールってどうやって作るんだっけなーとふと思って調べたのでメモ。

◆ どんなコントロールを作るか

タイトル行付きの StackPanel を作ってみる。イメージは以下。タイトルとカラーはプロパティで指定できるようにする。

f:id:shuntaro3:20180930233254p:plain

<Window
    x:Class="CustomStackPanel.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="450"
    Height="450"
    mc:Ignorable="d">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <!--ここから-->
        <Border
            Grid.Row="0"
            BorderBrush="SkyBlue"
            BorderThickness="3">
            <StackPanel>
                <Label Content="グループ1" Foreground="SkyBlue" />
                <Border
                    Margin="5"
                    BorderBrush="SkyBlue"
                    BorderThickness="0,0,0,2" />
                <StackPanel Margin="10">
                    <!--ここは可変-->
                    <TextBlock x:Name="text1" Text="あいうえお" />
                    <TextBlock x:Name="text2" Text="あいうえお" />
                    <TextBlock x:Name="text3" Text="あいうえお" />
                    <TextBlock x:Name="text4" Text="あいうえお" />
                    <TextBlock x:Name="text5" Text="あいうえお" />
                    <!--ここは可変-->
                </StackPanel>
            </StackPanel>
        </Border>
        <!--ここまで部品化-->
    </Grid>
</Window>

◆ 手順

1. UserControl を追加

プロジェクト -> ユーザコントロールの追加 で UserControl を新規作成する。Xaml ファイルとそれに紐づく C# のソースが生成される。UserControl 名は「GroupPanel」としている。

2. Xaml 修正

作りたいコントロールに合わせて、Xaml を修正していく。今回は子要素を持つ必要があるので、Template と ContentPresenter を使用している。以下を参考にした。

c# - Fill Stackpanel inside Usercontrol - Stack Overflow

Xaml はこんな感じになる。

GroupPanel.xaml

<UserControl
    x:Class="CustomStackPanel.GroupPanel"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="GroupPanelRoot">
    <UserControl.Template>
        <ControlTemplate TargetType="UserControl">
            <Border BorderBrush="{Binding StyleColor, ElementName=GroupPanelRoot}" BorderThickness="3">
                <StackPanel>
                    <Label Content="{Binding Title, ElementName=GroupPanelRoot}" Foreground="{Binding StyleColor, ElementName=GroupPanelRoot}" />
                    <Border
                        Margin="5"
                        BorderBrush="{Binding StyleColor, ElementName=GroupPanelRoot}"
                        BorderThickness="0,0,0,2" />
                    <StackPanel Margin="10">
                        <ContentPresenter Content="{TemplateBinding Content}" />
                    </StackPanel>
                </StackPanel>
            </Border>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

3. プロパティの追加

コントロールの見た目はできたので、あとは独自プロパティを追加する。独自プロパティは、追加した UserControl の C# 側に記載すれば OK。詳細は、依存関係プロパティとかでググれば出てくる。

GroupPanel.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace CustomStackPanel
{
    /// <summary>
    /// GroupPanel.xaml の相互作用ロジック
    /// </summary>
    public partial class GroupPanel : UserControl
    {
        public static DependencyProperty StyleColorProperty =
            DependencyProperty.Register(
                "StyleColor",
                typeof(Brush),
                typeof(GroupPanel),
                new PropertyMetadata(new SolidColorBrush(Colors.Transparent)));

        public Brush StyleColor
        {
            get => (Brush)GetValue(StyleColorProperty);
            set { SetValue(StyleColorProperty, value); }
        }

        public static DependencyProperty TitleProperty =
            DependencyProperty.Register(
                "Title",
                typeof(string),
                typeof(GroupPanel),
                new PropertyMetadata(""));

        public string Title
        {
            get => (string)GetValue(TitleProperty);
            set { SetValue(TitleProperty, value); }
        }

        public GroupPanel()
        {
            InitializeComponent();
        }
    }
}

4. 子要素に Name 属性が持てない!

以上までで良し良し楽勝だなとか思っていたら、問題発生。作成した UserControl の子要素に Name 属性を持たせたらエラーが発生した。嘘だと言ってよバーニー。

こんな感じの Xaml をかくと、

<local:GroupPanel
    Title="グループ2"
    Grid.Row="1"
    StyleColor="IndianRed">
    <StackPanel>
        <TextBlock x:Name="text1" Text="あいうえお" />
    </StackPanel>
</local:GroupPanel>

こんなエラーが出てくる。

重大度レベル   コード   説明  プロジェクト  ファイル    行 抑制状態
エラー   XEC0030 名前 "text1" は既にこのスコープで定義されています。    CustomStackPanel    MainWindow.xaml 41

なんでや。

5. 解決策

意味が分からなさ過ぎて、ネットの海をさまよっていたら、以下サイトたちに行き着いた。

How to create a WPF UserControl with Named content?

JD’s Blog » WPF: Cannot set Name attribute

記事によると、Xaml で画面作るとダメっぽいので、画面も C# 側で書け、以上。まじかよ。

Xaml なしの UserControl クラス(GroupPanel2) を作成してみたら、解決した。全然納得できないけど、しょうがないね。

GroupPanel2.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace CustomStackPanel
{
    public class GroupPanel2 : UserControl
    {
        public static DependencyProperty StyleColorProperty =
            DependencyProperty.Register(
                "StyleColor",
                typeof(Brush),
                typeof(GroupPanel2),
                new PropertyMetadata(new SolidColorBrush(Colors.Transparent)));

        public Brush StyleColor
        {
            get => (Brush)GetValue(StyleColorProperty);
            set { SetValue(StyleColorProperty, value); }
        }

        public static DependencyProperty TitleProperty =
            DependencyProperty.Register(
                "Title",
                typeof(string),
                typeof(GroupPanel2),
                new PropertyMetadata(""));

        public string Title
        {
            get => (string)GetValue(TitleProperty);
            set { SetValue(TitleProperty, value); }
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);

            Border parentBorder = new Border
            {
                BorderBrush = StyleColor,
                BorderThickness = new Thickness(3),
            };

            StackPanel mainStack = new StackPanel();

            Label titleLabel = new Label
            {
                Content = Title,
                Foreground = StyleColor,
            };

            Border lineBorder = new Border
            {
                Margin = new Thickness(5),
                BorderBrush = StyleColor,
                BorderThickness = new Thickness(0, 0, 0, 2)
            };

            StackPanel contentStack = new StackPanel
            {
                Margin = new Thickness(10)
            };

            ContentPresenter contentP = new ContentPresenter
            {
                Content = Content
            };

            contentStack.Children.Add(contentP);

            mainStack.Children.Add(titleLabel);
            mainStack.Children.Add(lineBorder);
            mainStack.Children.Add(contentStack);

            parentBorder.Child = mainStack;

            Content = parentBorder;
        }
    }
}

6. その他

サンプルソースは以下。見たい方はどうぞ。

github.com

◆ まとめ

無理やり感あるけど、なんとか解決できて良かった。カスタムコントロール+スタイルで作る方がもしかしてスタンダードなやり方なのかもしれない。そっちのやり方も試したらのっける。

ditaa を使って図を描く

http://ditaa.sourceforge.net/images/logo.png

◆ はじめに

先日 mermaid を使った図を描く話をブログに書いたが、もっと良いツール ditaa を大学時代の先生から教えてもらったので、メモ。

◆ ditaa とは

ditaa(DIagrams Through Ascii Art) とは、アスキーアートで書かれた図を画像に変換するツール。ちなみに、Java で作成されている。詳細は、作者の Github を参照。

github.com

◆ 構築環境

◆ 手順

1. Java のインストール

ditaa 自体は、jar ファイルで提供されているので、動作させるために Java 実行環境が必要。Java 1.5 以上であれば動作する。

  1. パッケージマネージャ Chocolatey のインストール
  2. choco コマンドで Java をインストール
$ choco install java

2. ツールのダウンロード

以下 URL からツールをダウンロードする。 本家版だと、日本語を入力する場合は一文字ごとに半角スペースいれないと図が崩れてしまう。 なので、日本語対応版のものをダウンロードする。

https://github.com/luozengbin/dot.emacs.d/raw/master/extra/org-ditaa/jditaa.jar

3. bat ファイルの作成

ツール実行時に、毎回 Java コマンド打つのが面倒なので、bat ファイル化してしまう。 環境変数への追加もお忘れなく。

ditaa.bat

@echo off
set jar=%~dp0jditaa.jar
java -Dfile.encoding=UTF-8 -jar %jar% %*

4. 変換元ファイルの作成

変換元ファイルは、テキストファイルとして作成する。ただし、文字コードUTF-8 で作成しないと変換後に文字化けしてしまう。

+--------+   +-------+    +-------+
|        | --+ ditaa +--> |       |
|  Text  |   +-------+    |diagram|
| 日本語 |   |!magic!|    |       |
|     {d}|   |       |    |       |
+---+----+   +-------+    +-------+
    :                         ^
    |       Lots of work      |
    +-------------------------+

5. 画像変換

ここまでできたら後はツールで変換するだけ。

$ ditaa test.txt test.png

test.png

f:id:shuntaro3:20180620212921p:plain

◆ まとめ

すごい便利。いろいろツール試したけど、やっぱり表現力が一番高いのは ditaa だった。普通に仕事で活用できると思うので、皆様もぜひ。

VSCode で Markdown + mermaid を使う

f:id:shuntaro3:20180519201507p:plain

◆ はじめに

最近、何でも markdown で書きたい欲が凄い。 ただ、markdown だと図とかが描けないので、そういう資料はパワポとかで作っていたが、バージョン管理ができなくて嫌というジレンマをずっと持っていた。 そんな折、簡単な図なら mermaid というツールで描けるということを思い出し、使ってみることにしたら、結構良かったのでメモ。 ちなみに、mermaid は以前参加した岡山 Ruby, Ruby on Rails 勉強会で教えてもらった。

◆ 構築環境

◆ 手順

1. VSCode 拡張機能Markdown Preview Enhanced」のインストール

VSCode拡張機能のタブから「Markdown Preview Enhanced」を入力し、インストール。簡単。インストール後は、再読み込みを忘れずに。

2. 記述とプレビュー

環境整ったので、簡単な業務フロー図を書いてみる。

記述方法は、公式サイトを見れば大体わかるし、以下記事の方が詳しいので是非みていただきたい。

VScodeでmermaidを使ったmarkdown資料作りメモ - Qiita


# 業務フロー図
## 発注フロー
```mermaid
sequenceDiagram
participant A as 店舗
participant B as 工場
participant C as 倉庫
A->>B: 発注
B->>B: 製造
B->>C: 出荷
C-->>A: 出荷
Note over A: 商品が来た!<br>やったね!
```


Preview 表示は、エディタ右上のアイコンからできる。VSCode デフォルトのプレビューアイコンと同じなので、ちょっとわかりにくいけど。 下図赤枠のところがアイコン。いい感じに図ができていることがわかる。

f:id:shuntaro3:20180519185026j:plain

3. HTML化

プレビュー表示画面で、右クリック->「HTML」->「HTML(offline)」を選択。 ファイルは、markdown ファイルと同じフォルダに出力される。

4. PDF化

HTML 化と同様に、プレビュー表示画面から以下操作で変換できる。

  • 右クリック -> 「Chome(Puppeteer)」 -> 「PDF」
  • 右クリック -> 「PhantomJS」 -> 「PDF」

ただし、phantomjs もしくは pupeteer のインストールが必要。どちらもヘッドレスブラウザ。

また、会社で試したときは、レイアウトが崩れてしまって、うまく PDF 化できなかったが、家でやったらうまくいった。 これは少し調査必要。 あと、phantomjs の方が PDF のファイルサイズがかなり小さい。

  • PhantomJS:10KB
  • Puppeteer:367KB

* PhantomJS のインストール

  1. パッケージマネージャ Chocolatey のインストール
  2. choco コマンドで PhantomJS をインストール
$ choco install phantomjs

実は Windows10 にはパッケージ管理の仕組みが存在するので、そっちから入れてもよい。 参考記事は以下。結局 Chocolatey 経由で入るっぽいので、自分自身は Chocolatey を使っている。

* pupeteer のインストール

  1. パッケージマネージャ Chocolatey のインストール
  2. choco コマンドで Node.js をインストール
  3. npm コマンドで Pupeteer をインストール
$ choco install nodejs
$ npm install -g puppeteer

◆ まとめ

素晴らしい時代になった。全ての資料がバージョン管理できるようになることを切に望む。 また、全然違う話になるが、markdown の中に markdown を書くのにはまった。 普通はインデント一段階深くするだけでできるはずなんだけど、それすると画像が表示されなくなったりして...。 結局<span>で囲って、エスケープごりごりで記載したが、もっと良い方法があれば知りたい。

オープンセミナー2018@岡山 に参加してきた話

f:id:shuntaro3:20180513125221p:plain

◆ はじめに

5/12(土)に開催されたオープンセミナー2018@岡山に参加してきた。 イベントの詳細は、公式ホームページをチェックだ!

今回のテーマは、「エンジニアの生存戦略と働き方」ということで、 自分こういうの大好物なんだよ。エモい話をもっておくれ、僕もするから状態。 また、登壇者も個性的なキャリアの方ばかり...。これは行くしかない。

ということで、登壇者の内容を簡単に3行くらいでまとめてみた。個人的な感想や意見が多いに含まれているので、注意。また、言ってた内容と違うやないかい!っていう突っ込みもあればください。あと、togetterまとめもあるので、ライブ感を感じたい人は是非。

◆ 内容まとめ

* したたか?天然?なんかうまくやってるITエンジニアの生存戦略

  • 名前:伊藤 淳一さん
  • 所属:株式会社ソニックガーデン プログラマ
  • 資料:https://speakerdeck.com/jnchito/number-oso2018
  • 内容:
    • 社外でも通用するエンジニアを目指す。今の会社がなくなってもやっていける?自分のスキルを証明するものある?
    • 自分自身(好きなことや嫌いなこと、強みや弱み)を知り、自分を売り込むって大事。周りはあなたが思っているほどあなたを見ていないし、気にしてもいない。
    • 一番の下手くそは実際なるとつらい。

* 小売-エンジニア-企画・設計-マーケと情報爆発が僕のキャリアに与えた影響について

  • 名前:中村哲也さん
  • 所属:NHN JAPAN株式会社 CloudGarageコミュニティマネージャー
  • 資料:ー
  • 内容:
    • 花屋→家具屋→コールセンター→インフラ→マーケター。
    • 評価軸を意識。家具屋とIT企業では評価軸が全然違う。
    • 生きろ!!

* エンジニア生存戦略〜地方エンジニアがこの先生き残るには〜

* 手持ち10万円から始めた年商8億円のITビジネス

* ぼくらがかんがえたさいきょうのリモート雑談環境

* 5分でわかるサブスレッド

  • 名前:古谷雅勝さん
  • 所属:株式会社サブスレッド デベロップメント事業部
  • 資料:ー
  • 内容:
    • 株式会社サブスレッド岡山オフィスの紹介
    • 環境良いぞ。
    • ゲーセンも布団屋もあるぞ。住めるぞ。

* [昼休憩] アンチボッチランチ

  • 昼休憩をボッチにさせない、運営側の神配慮。ボッチ4人グループで昼飯食べる。圧倒的良い企画。
  • 幸運にも伊藤さんとご一緒できた。
  • 田舎暮らしとかPTAとかの話で盛り上がった。折角なので技術話をしたほうが良かった気もしたが、楽しかったから〇。

* 縄文とIT、美と技術

  • 名前:ボレロ村上(村上原野)さん
  • 所属:縄文造形家 猪風来美術館陶芸講師
  • 資料:ー
  • 内容:(うまくまとめられない。独特の雰囲気の凄いプレゼンだった。)
    • 縄文造形家とは。縄文についてのレクチャー。装飾ではない、造形だ。
    • 現代縄文土器づくりの歴史は、UNIXよりも新しい。
    • 壮大なエンディング。人間の歩みが止まることはない。業。

* 農業をやってみた

  • 名前:ひらさん
  • 所属:ひら農場
  • 資料:ー
  • 内容:
    • 好きを守れ。仕事は嫌でなければ何でもよい。不安からぶっちぎりで逃げる。
    • 作物はバージョンアップしない。キャベツ2.0 にはならない。
    • 農業はソフトウェア開発と同じ。開発プロセスはいろいろなところに活用できる。

* 地方の受託会社の会社員の派手めな業務外活動とその効用

  • 名前:前川昌幸さん
  • 所属:株式会社イー・ネットワークス CPI エバンジェリスト
  • 資料:ー
  • 内容:
    • 会社員でも知ってもらうことは重要。
    • これを頼める人として認識してもらうの大事。
    • たまたまでもやってみて実績を。やらない理由はやる前に、得られるものはやった後にしかやってこない。

* ゼロから始める技術書執筆

  • 名前:湊川あいさん
  • 所属:フリーランスWebデザイナー マンガ家 技術書執筆
  • 資料:https://www.slideshare.net/AiMinatogawa/by-69678890
  • 内容:(凄い勉強されている(小並感))
    • フラストレーション駆動。闇の力を光の力に。(ヒュンケルかな?)泣くほど悔しいこと、むっとすることは、自分のやりたいことの裏返し。
    • 誰のために書くか。3C分析。市場にすぐ出してフィードバックを得て、精度を高める。
    • ANDの才能。組み合わせで誰しもみんなNo1になれるはず。ORの抑圧に負けるな。

* GitHubber@日本

  • 名前:池田尚史さん
  • 所属:GitHub Solutions Engineer
  • 資料:https://speakerdeck.com/ikeike443/githubber-at-ri-ben
  • 内容:
    • 本当に技術だけを追求したいのか。エンジニアには幅広い選択肢がある。
    • できないからやらないは負のループ。抜け出すには何かしらジャンプアップしかない。
    • キーミックス。ミックスすればするほど希少価値が上がる。あと英語ができると見える世界が変わる。やればできるようになる。

◆ まとめ

全体的な感想としては、わかりみしかなかった。わかりみしかなかった割りに、今の自分とのギャップが大きいのは、結局のところ自分は何もしていない、何も行動していないからなんだと思う。そういう人って結構多いと思う。そういう人は、Github池田さんも言われていたが、為したいことが強制的にできる環境にジャンプするのが手っ取り早いよね。自分自身への理解を深め、何をすればよいかを決め、やる。言葉で書くと簡単だけど、難しい。

あと、皆さん(自分も含め)エモい話好きだなー。エモい話だけで終わったら意味ないけどな(自戒)。

何はともあれ、自分のキャリアを考え直す良い機会になった。変わるぞ。