より良いエンジニアを目指して

1日1つ。良くなる!上手くなる!

WindowChromeでキャプションバーなしのウインドウを作る

最近のアプリケーションはキャプションバーがありません。

f:id:rimever:20191123214651p:plain

WPFでは、WindowChromeを使えば出来ます。

docs.microsoft.com

さっと書けないので自分用のメモ。

XAML

<Window x:Class="WpfApp1.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"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance local:MainWindowViewModel}"
        Title="MainWindow" Height="350" Width="525" SnapsToDevicePixels="True">
    <Window.Resources>
        <Style x:Key="CaptionButtonStyleKey" TargetType="{x:Type Button}">
            <Setter Property="OverridesDefaultStyle" Value="True" />
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="FontFamily" Value="Marlett"/>
            <Setter Property="IsTabStop" Value="False"/>
            <Setter Property="HorizontalContentAlignment" Value="Center" />
            <Setter Property="VerticalContentAlignment" Value="Center" />
            <Setter Property="Margin" Value="2" />
            <Setter Property="Padding" Value="1" />
            <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border x:Name="border" Background="Transparent" SnapsToDevicePixels="True">
                            <Border.Effect>
                                <DropShadowEffect Opacity="0"/>
                            </Border.Effect>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal">
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border">
                                                <EasingDoubleKeyFrame KeyTime="0" Value="0.6"/>
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver">
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border">
                                                <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border">
                                                <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                                            </DoubleAnimationUsingKeyFrames>
                                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.ShadowDepth)" Storyboard.TargetName="border">
                                                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                                            </DoubleAnimationUsingKeyFrames>
                                            <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.Color)" Storyboard.TargetName="border">
                                                <EasingColorKeyFrame KeyTime="0" Value="White"/>
                                            </ColorAnimationUsingKeyFrames>
                                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.Opacity)" Storyboard.TargetName="border">
                                                <EasingDoubleKeyFrame KeyTime="0" Value="0.6"/>
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Disabled"/>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <ContentPresenter x:Name="contentPresenter" Focusable="False" Margin="{TemplateBinding Padding}"
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        </Style>
    </Window.Resources>
    <WindowChrome.WindowChrome>
        <WindowChrome CaptionHeight="{x:Static SystemParameters.CaptionHeight}"
                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
    </WindowChrome.WindowChrome>
    <Border BorderBrush="Gray"
            BorderThickness="1">
        <Grid>
            <StackPanel Orientation="Horizontal" Margin="5"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Top">
                <Button Content="0" Style="{DynamicResource CaptionButtonStyleKey}" Click="ClickWindowMinimize"/>
                <Button Content="1" Style="{DynamicResource CaptionButtonStyleKey}" Click="ClickWindowMaximize"/>
                <Button Content="2" Style="{DynamicResource CaptionButtonStyleKey}" Click="ClickWindowNormal"/>
                <Button Content="r" Style="{DynamicResource CaptionButtonStyleKey}" Click="ClickClose" />
            </StackPanel>
        </Grid>
    </Border>
</Window>

ビハインドコード xaml.cs

#region

using System.Windows;

#endregion

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            // ViewModel
            DataContext = new MainWindowViewModel(); 
            MouseLeftButtonDown += (sender, e) => DragMove();
        }

        private void ClickClose(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void ClickWindowMinimize(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Minimized;
        }

        private void ClickWindowMaximize(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Maximized;
        }

        private void ClickWindowNormal(object sender, RoutedEventArgs e)
        {
            WindowState = WindowState.Normal;
        }
    }
}

f:id:rimever:20191123221911p:plain

参考記事

grabacr.net

Autofacを利用して、メソッド呼び出し時にログ出力

メソッド呼び出し時にログを出力してみたいなと探したところ、Autofacというライブラリを使えば、.NET Coreでも行けそうだということで試してみました。

このライブラリそのものはIoCのためのライブラリです。

やってみた感触としては、

  • 出力したいメソッドにvirutalの指定が必要で、これが微妙。
  • internalなクラスに適用するためにフレンドアセンブリの指定が必要。

でした。

f:id:rimever:20191123180252p:plain

autofaccn.readthedocs.io

using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Autofac;
using Autofac.Extras.DynamicProxy;
using Castle.DynamicProxy;

// internalなクラスにも適用出来るためフレンドアセンブリの指定が必要。
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<First>()
                .EnableClassInterceptors();
            builder.Register(c => new CallLogger(Console.Out));
            var container = builder.Build();
            using (var scope = container.BeginLifetimeScope())
            {
                var first = scope.Resolve<First>();
                first.GetValue();
                Console.WriteLine("Hello World!");
                
            }
        }
    }
    [Intercept(typeof(CallLogger))]
    internal class First
    {
        public virtual int GetValue()
        {
            return 5;
        }
    }

    public class CallLogger : IInterceptor
    {
        readonly TextWriter _output;

        public CallLogger(TextWriter output)
        {
            _output = output;
        }

        public void Intercept(IInvocation invocation)
        {
            _output.Write("Calling method {0} with parameters {1}... ",
                invocation.Method.Name,
                string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));

            invocation.Proceed();

            _output.WriteLine("Done: result was {0}.", invocation.ReturnValue);
        }
    }
}
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp2.2</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Autofac.Extras.DynamicProxy" Version="4.5.0" />
      <PackageReference Include="Castle.Core" Version="4.4.0" />
    </ItemGroup>

</Project>

仕組みとしては、DIを解決して生成されたオブジェクトは、指定されたクラスの純粋のインスタンスではありません。

MethodIntercepterで指定されたメソッドを注入したオブジェクトになります。

f:id:rimever:20191123175742p:plain
FirstProxy

参考記事

stackoverflow.com

出欠管理ツールに調整さんはお手軽でオススメ。

大人数で飲み会となると、まず行うのが「日程調整」です。

プライベートでの飲み会だったら、LINEグループで、LINEの機能で日程調整が出来るので、これで行ってました。

会社の飲み会の調整では、Googleスプレッドシートを用意して、みんなに入力してもらうのですが、「調整さん」というのがあります。

オススメです。

この手の Webサービスにありがちな登録の手間がありません。

とりあえず、日程調整だけをしたいのであればオススメです。

イラスト表示機能

イラスト表示機能があり、入力したメンバーのイラストが表示する機能があります。

f:id:rimever:20191122071746p:plain
なんだ、このキャラは!

メンバーが多くなると、かなり賑やかで楽しくなって来るので、この設定はオンにすることがオススメです。

完全ランダムでは無く、名前の文字列からイラストが決まるので、気に入らなければ名前の後に!とか英字で試してみると良いです。

セキュリティ

アクセス元のIPと端末で入力者を特定し、幹事なら、イベント作成後も設定が変更出来るようです。

セキュリティについては、URLさえわかれば誰でも見れることになってしまいますので、個人情報や詳細情報は記載することは避けた方が良いと思います。

日程調整が終わったら、CSVで出欠者をダウンロードして、ここからはやはりスプレッドシートですかね。

double.TryParseとCultureInfo.InvariantCulture

文字列を小数値に変換するとなると以下のようなコードになります。

double.Parseもありますが失敗すると例外を投げてしまうので、TryParseを使います。

using System;
using System.Globalization;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string text = "123.4";
            if (!double.TryParse(text,out var result))
            {
                Console.WriteLine($"{text}は数字ではありません。");
                return;
            }
            Console.WriteLine($"{result}に変換されました。");
        }
    }
}

f:id:rimever:20191118212406p:plain

このTryParseではCultureInfoやNumberStylesを指定することができます。

こんなの使うことあるのか?

あるから用意されています。

例えばこんなコード

using System;
using System.Globalization;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // フランス語の数値
            string text = 123.4.ToString(new CultureInfo("fr-fr"));
            if (!double.TryParse(text, out var result))
            {
                Console.WriteLine($"{text}は数字ではありません。");
                return;
            }
            Console.WriteLine($"{text}は{result}に変換されました。");
        }
    }
}

f:id:rimever:20191118212744p:plain
もちろん、環境は日本語です。

小数値をフランス語形式の文字列に表示して、日本語環境で数値に変換します。

元の123.4にはなりません。

というのもフランス語では小数値は123,4 となるためです。

なので、言語の違いはズレを生んでしまうことになります。多様性も良いことづくめな訳がないのです。

というわけで共通に揃える必要が出てきます。CultureInfo.InvariantCultureの出番です。

using System;
using System.Globalization;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string text = 123.4.ToString(CultureInfo.InvariantCulture);
            if (!double.TryParse(text,NumberStyles.Number,CultureInfo.InvariantCulture, out var result))
            {
                Console.WriteLine($"{text}は数字ではありません。");
                return;
            }
            Console.WriteLine($"{text}は{result}に変換されました。");
        }
    }
}

f:id:rimever:20191118213116p:plain

CultureInfo.InvariantCultureInfoは特定の国や言語圏に依存しないため、端末がどういった環境でも左右されないこと保証することができます。

docs.microsoft.com

私が悩んだ上司との付き合い方と出した答え

以前の職場ですが、上司との付き合い方がわからず、悩んだことがあります。

上司「これ頼んだつもりだったんだけど」

私「言われてない・・・」

みたいな、阿吽の呼吸、暗黙の了解を求められるようなことや、「この人、何がしたいんだ?」という、さっぱりわからない人でした。

色々、上司とは何か、と本を読んだりしたものでした。

アメリカでは上司に贈り物をして評価を得たりするという本もありました。

以下の本では、ベトナム戦争アメリカ軍の上官が部下に撃たれることも発生して、リーダーのあり方が変わって、部下と共に戦うリーダーが紹介されています。

honto.jp

本を読んだところで何も前進できず、転職することになりました。

今の会社に来て気づいたのは、何か問題があったら、まず上司に報告するものだ、ということ。

前職では、大きな問題でなければ報告しないというスタンスでした。

報告すれば、上司は相談に乗ってくれますし、適宜、調整を図ってくれます。

以下の記事が思い浮かびます。

simplearchitect.hatenablog.com

前職でももう少し上司とコミュニケーションを図ってマネージャーを利用するくらいの気概で取り組めば良かったかもなとも思います。

私も上司が忙しそうだからと変に気を遣って自分の中で解決しようとしてしまってばかりいました。

が、コミュニケーションを取っても今の上司のようには動けないだろうな、とも感じてます。

転職先の上司が出来るマネージャーとは限りませんが、私の場合は結果的に転職がベストアンサーでした。

bashの文字色変更について自分の手を動かして基本を学んでみる

bashの文字色変更についてです。

Windowsコマンドプロンプトと比べると一見すると複雑です。

rimever.hatenablog.com

いまいち、記事を読んでも分からなかったので自分の手で動かしてみることにしました。

echo -e "¥e[31m red ¥em"

f:id:rimever:20191118220841p:plain

¥e[色属性mで文字の属性を宣言します。最後に¥emでリセットします。

この色属性というのが厄介です。

31は文字色が赤、32は文字色が緑、33が文字色が黄色です。

41は背景が赤、42が背景が緑、43が背景が黄色です。

じゃ、最初の3が文字色で次の数字が色?

と思いきや、そうでもなかったりします。91などでも文字色が変えられますし、もっと細かい色指定もできます。

継ぎ足し、継ぎ足しの産物でしょう。

misc.flogisoft.com

ひとまずは、基本は3xと4xで、良さそうです。

下一桁の色は、以下です。

下一桁の数字
0 Black
1 Red
2 Green
3 Yellow
4 Blue
5 Magenta
6 Cyan
7 Light gray

文字色と背景色を組み合わせる時は;で挟みます。

echo -e "¥e[31;44m red ¥em"

f:id:rimever:20191118221927p:plain

参考記事

www.m-bsys.com

qiita.com

トヨタ生産方式の「ムダ」と「むだ」から考えるシステム開発

最近は「ポケット図解トヨタ方式の基本がわかる本」というのを読んでいました。

ポケット図解 トヨタ方式の基本がわかる本 (Shuwasystem Business Guide Book)

ポケット図解 トヨタ方式の基本がわかる本 (Shuwasystem Business Guide Book)

トヨタ生産方式には、2つの無駄があると言います。

  • 目に見える無駄がカタカナの「ムダ」
  • 工程を改善して解消されるのがひらがなの「むだ」

「ムダ」は、作り過ぎ、手持ち、運搬、加工、在庫、動作、不良を生むという7つのカテゴリーに分類されています。

(その「ムダ」と「むだ」を排除するために、標準作業というのがあるのですが、それは割愛)

システム開発に置き換えて考えてみても、そういうのあるなあと思います。

  • PCスペックが低過ぎて、仕事にならん
  • 運搬ではないが、割り込みのメールや電話
  • システム不具合

など。

「むだ」というのも思い当たるのはあります。

以前の会社で、自社でもなくクラウド上にあるリポジトリなのに自社からではないとコミット出来ないという謎ルールがあったような記憶があります。

古い体質の企業だとプログラムをDVDに焼いて提供するとか。

これらは極端ですが、「ムダ」、「むだ」に感じるようなことがあれば無駄取りをしていきたいと思います。

また、無駄取りをしていける職場で働きたいですね。