20200426のC#に関する記事は10件です。

C# Enumの文字列変換は重い

EnumをToStringすると激重なので、事前にswitch文やarrayで文字列を定義しておくと、パフォーマンスを上げることができる

コード

[RankColumn]
public class EnumTest
{
    [Params(Weapon.WeaponType.A)]
    public Weapon.WeaponType weaponType;

    [Benchmark]
    public string Case1()
    {
        return weaponType.GetNameSwitch();
    }

    [Benchmark]
    public string Case2()
    {
        return weaponType.GetNameArray();
    }

    [Benchmark]
    public string Case3()
    {
        return weaponType.GetNameToString();
    }
}

public static class Weapon
{
    public enum WeaponType
    {
        A,
        B,
        C,
    }

    private static string[] weaponTypeArray = {
        "A",
        "B",
        "C",
    };

    public static string GetNameSwitch(this WeaponType type)
    {
        switch (type)
        {
            case WeaponType.A: return "A";
            case WeaponType.B: return "B";
            case WeaponType.C: return "C";
        }
        throw new NotImplementedException();
    }

    public static string GetNameArray(this WeaponType type)
    {
        return weaponTypeArray[(int)type];
    }

    public static string GetNameToString(this WeaponType type)
    {
        return type.ToString();
    }
}

結果

//BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
//Intel Core i5-9600K CPU 3.70GHz(Coffee Lake), 1 CPU, 6 logical and 6 physical cores
// [Host]     : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
//  DefaultJob : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT

//| Method | weaponType |        Mean |     Error |    StdDev | Rank |
//|------- |----------- |------------:|----------:|----------:|-----:|
//|  Case1 |          A |   1.2287 ns | 0.0263 ns | 0.0246 ns |    2 |
//|  Case2 |          A |   0.0523 ns | 0.0123 ns | 0.0115 ns |    1 |
//|  Case3 |          A | 282.2946 ns | 1.4594 ns | 1.3652 ns |    3 |
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# Stringパフォーマンス計測

よく使うintから文字列への変換Performance測定

計測コード

[MemoryDiagnoser, RankColumn]
public class ScoreBoard
{
    [Params(999999)]
    public int score;

    [Benchmark]
    public string Case1()
    {
        return "Score : " + score.ToString();
    }

    [Benchmark]
    public string Case2()
    {
        return "Score : " + score;
    }

    [Benchmark]
    public string Case3()
    {
        string scoreText = score.ToString();
        return string.Format("Score : {0}", scoreText);
    }

    [Benchmark]
    public string Case4()
    {
        return string.Format("Score : {0}", score);
    }

    [Benchmark]
    public string Case5()
    {
        return $"Score : {score}";
    }

    [Benchmark]
    public string Case6()
    {
        string scoreText = score.ToString();
        return $"Score : {scoreText}";
    }

    [Benchmark]
    public string Case7()
    {
        string scoreText = score.ToString();
        return ZString.Concat("Score : " + scoreText);
    }

    [Benchmark]
    public string Case8()
    {
        return ZString.Concat("Score : ", score);
    }

    [Benchmark]
    public string Case9()
    {
        string scoreText = score.ToString();
        return ZString.Format("Score : {0}", scoreText);
    }

    [Benchmark]
    public string Case10()
    {
        return ZString.Format("Score : {0}", score);
    }
}

結果

//BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
//Intel Core i5-9600K CPU 3.70GHz(Coffee Lake), 1 CPU, 6 logical and 6 physical cores
// [Host]     : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
//  DefaultJob : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT

//| Method |  score |      Mean |    Error |   StdDev | Rank |  Gen 0 | Gen 1 | Gen 2 | Allocated |
//|------- |------- |----------:|---------:|---------:|-----:|-------:|------:|------:|----------:|
//|  Case1 | 999999 |  59.98 ns | 0.670 ns | 0.627 ns |    2 | 0.0204 |     - |     - |      96 B |
//|  Case2 | 999999 |  60.23 ns | 0.331 ns | 0.310 ns |    2 | 0.0204 |     - |     - |      96 B |
//|  Case3 | 999999 | 132.33 ns | 1.099 ns | 0.975 ns |    5 | 0.0203 |     - |     - |      96 B |
//|  Case4 | 999999 | 139.31 ns | 1.503 ns | 1.406 ns |    6 | 0.0253 |     - |     - |     120 B |
//|  Case5 | 999999 | 139.09 ns | 0.712 ns | 0.666 ns |    6 | 0.0253 |     - |     - |     120 B |
//|  Case6 | 999999 |  61.18 ns | 0.730 ns | 0.683 ns |    2 | 0.0204 |     - |     - |      96 B |
//|  Case7 | 999999 | 117.34 ns | 0.965 ns | 0.903 ns |    4 | 0.0323 |     - |     - |     152 B |
//|  Case8 | 999999 |  51.19 ns | 0.522 ns | 0.488 ns |    1 | 0.0119 |     - |     - |      56 B |
//|  Case9 | 999999 | 196.12 ns | 1.129 ns | 1.056 ns |    7 | 0.0203 |     - |     - |      96 B |
//| Case10 | 999999 | 108.29 ns | 0.723 ns | 0.676 ns |    3 | 0.0118 |     - |     - |      56 B |

ZStringが使えるならこれを使ったほうが一番パフォーマンスがいい。
無理なら
ToString() と +演算子の組み合わせ
ToString() と $ - 文字列補間がパフォーマンスが良い

文字列の変換数が増えるとパフォーマンスは変わってくるが...

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

Unityを使ってクリエィティブなゲームを作れるようになるまで。(2日目)

題名を(〇日目)のようにしました。
色んなゲームを作る予定ですので、私の成長過程を楽しんでもらえたら幸いです!:relaxed:

今日は何を作るの?

ネットサーフィンをしていると、なんとN予備校の受講が無料らしいとの事!!

image.png
引用:N予備校
  https://www.nnn.ed.nico/

中を漁ってみると、なんとunityのコースが!
早速受講してゲームを作ろう!

作成したゲームはこれ

unity_chan.gif

受講したコース通り、キャラクターの後ろに付いている赤い球を箱にぶつけるゲームを作りました。

学んだこと

  • Terrainオブジェクトを使用して、簡単に地形が作れる。
  • Spring jointでキャラクターと玉の位置関係、バネの力で制御することができる。
  • scriptの中のpublicで定義された変数・クラスは、GUIから代入することができる。
  • ↑の仕組みであるため、script内にconstructorは持たない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】UIの透過をスクリプトからまとめて調整するCanvasGroup

環境

Unity 2019.3.7f1

はじめに

ボタンの透過率を変更しても
ボタンに付けているテキストの透過率は一緒に変化しません。

まとめて透過率を変更したいときはCanvasGroupが便利です。

手順

1.透過値を変更したいUIを適当オブジェクトにまとめる
2.まとめたオブジェクトにCanvasGroupコンポーネントを追加
3.スクリプトからCanvasGroupコンポーネントを取得しアルファ値を変更する
 CanvasGroup型の変数.alpha=0f~1f;

具体例

3つのボタンの透過値を実行後50%に変更していきます。

1.ボタンを3つ作成
image.png
 
2.CanvasにCanvasGroupコンポーネントを追加
image.png

CanvasGroupコンポーネントが追加されました。
image.png

3.スクリプト作成
スクリプトを新規作成します。スクリプト名はCanvasTestとしました。

CanvasTest
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;//UIを使うとき追加

public class CanvasTest : MonoBehaviour
{
    [SerializeField] private CanvasGroup a;//CanvasGroup型の変数aを宣言 あとでCanvasGroupをアタッチする

    void Start()
    {
        a.alpha = 0.5f;//変数aのalpha値を変更
    }
}

4.空のゲームオブジェクト作成&スクリプトをアタッチ
image.png

5.スクリプトにCanvasGroupをアタッチ
Canvasを空のオブジェクトのスクリプトの変数a欄にアタッチ
image.png

 
CanvasGroupの場所を覚えさせることができました。
image.png

6.実行結果
透過率がまとめて変わりました~。
image.png

おわりに

個別の透過率変更とはサヨナラです♪

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

【Unity】否定演算子の使い方

環境

Unity 2019.3.7f1

はじめに

否定演算子の使い方がわからなくなるのでメモ

使い方

使い方は2種類
・if文で使用
 if(変数 != 数値)
 {
  処理内容
 }

・trueとfalseを逆にする
 変数 = !変数;

具体例

・if文で使用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class test : MonoBehaviour
{
    int a;

    void Start()
    {
        a = 2;

        //aが1ではないなら処理
        if(a != 1)
        {
            Debug.Log("aは1じゃないよ");//コンソールに表示
        }       
    }
}

実行結果
image.png

 
・trueとfalseを逆にする

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class test : MonoBehaviour
{   
    bool a;

    void Start()
    {
        a = true;

        a = !a;
        Debug.Log("a=" + a);
        a = !a;
        Debug.Log("a=" + a);
        a = !a;
        Debug.Log("a=" + a);
        a = !a;
        Debug.Log("a=" + a);        
    }
}

実行結果
image.png

おわりに

なにかと使える否定演算子

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

待望の UWP 向け(厳密には Uno Platform 向け)Prism を使ってみよう

個人的に長らく望んでいた UWP 版 Prism ですが、このたび Prism の Uno Platform 対応という形の Pull request がマージされました。そのおかげで UWP でも Prism が使えるようになりました。なんといっても Uno Platform は UWP のコードを Android/iOS/WebAssembly などで動かすプロジェクトだからね!

該当のPR: Add support for Uno Platform and WinUI #2054

こちらですが、Prism 8.0 での公開が予定されています。なので現段階では以下の MyGet からプレビュー版を入手して使う必要があります。

https://myget.org/gallery/prism

ただ、これを書いている時点の MyGet の Uno 用の Prism は参照に追加しただけでビルドエラーになってしまいます。そのビルドエラーになる修正自体はソースに反映されていて、Prism の Azure DevOps のビルドパイプラインでビルドされているので、そのビルド成果物の NuGet をダウンロードして、そこからパッケージを追加するようにしました。

私が試したのは #1717 のビルドパイプラインの成果物から NuGet のパッケージをダウンロードしてます。

では、早速使ってみましょう。UWP で DI コンテナーを Unity にしたい場合は Prism.Unity.Uno になります。

image.png

とりあえず MainPage.xaml はいらないので消します。そして Views フォルダーを作って、そこに Shell という名前でページを作りましょう。

そして、App.xaml を PrismApplication タグに置き換えます。

App.xaml
<prism:PrismApplication
    x:Class="PrismHelloWorld.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:prism="using:Prism.Unity">
</prism:PrismApplication>

App.xaml.cs も PrismApplication に親クラスを置き換えて Prism ではおなじみのメソッドをオーバーライドします。とりあえず、先ほど作った Shell をメインの画面となるように CreateShell メソッドの戻り値として返すようにしました。

App.xaml.cs
using Prism.Ioc;
using Prism.Unity;
using PrismHelloWorld.Views;
using Windows.UI.Xaml;

namespace PrismHelloWorld
{
    sealed partial class App : PrismApplication
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override UIElement CreateShell() => Container.Resolve<Shell>();

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }
    }
}

現状のソリューションエクスプローラーはこんな感じです。

image.png

では、実行してみましょう。

image.png

ちゃんとウィンドウが出ました!!

リージョンとモジュールの追加

では、Prism の画面遷移の機能であるリージョンを追加します。
Shell.xaml のコードを以下のようにしてリージョンを追加します。

Shell.xaml
<Page
    x:Class="PrismHelloWorld.Views.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PrismHelloWorld.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:regions="using:Prism.Regions"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Text="The hello world app"
                   Style="{StaticResource HeaderTextBlockStyle}" />
        <ContentControl Grid.Row="1"
                        regions:RegionManager.RegionName="ContentRegion" />
    </Grid>
</Page>

動かすとこんな感じになります。まだリージョンに何もページを追加してないので、TextBlock が表示されているだけです。

image.png

ソリューションにクラスライブラリープロジェクトを追加して、Prism.Unity.Uno パッケージを追加します。Class1.cs は消して MainModule という名前でクラスを作ります。
そして IModule インターフェースを実装します。

MainModule.cs
using Prism.Ioc;
using Prism.Modularity;

namespace PrismHelloWorld.Main
{
    public class MainModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }
    }
}

ここで登録する画面を作りましょう。Views フォルダーを作って MainView という名前のユーザーコントロールと、ViewModels フォルダーを作って MainViewModel という名前のクラスを作成します。

MainViewModel.cs は適当に以下のように作りました。

MainViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using Prism.Services.Dialogs;

namespace PrismHelloWorld.Main.ViewModels
{
    public class MainViewModel : BindableBase
    {
        public string Message => "Hello from MainViewModel";
    }
}

MainView.xaml.cs でコンパイル時データバインディング使うために DataContext を MainViewModel にキャストして返す ViewModel プロパティを定義します。

MainViewModel.cs
using PrismHelloWorld.Main.ViewModels;
using Windows.UI.Xaml.Controls;

namespace PrismHelloWorld.Main.Views
{
    public sealed partial class MainView : UserControl
    {
        private MainViewModel ViewModel => (MainViewModel)DataContext;
        public MainView()
        {
            this.InitializeComponent();
        }
    }
}

MainView.xaml のほうは、AutoWireViewModel の設定をして、適当に ViewModel の Message とバインドするための TextBlock を置きました。

MainView.xaml
<UserControl
    x:Class="PrismHelloWorld.Main.Views.MainView"
    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:mvvm="using:Prism.Mvvm"
    mvvm:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <StackPanel>
        <TextBlock Text="{x:Bind ViewModel.Message}" />
    </StackPanel>
</UserControl>

画面が出来たので MainModule.cs でコンテナーに登録したりリージョンに表示するように設定してみたいと思います。
なんか、ここら辺書いてると WPF の Prism で色々やってたころを思い出して懐かしくなってきますね。

MainModule.cs
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using PrismHelloWorld.Main.Views;

namespace PrismHelloWorld.Main
{
    public class MainModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RegisterViewWithRegion("ContentRegion", typeof(MainView));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }
    }
}

モジュールが出来たのでアプリ側から読み込むようにしましょう。モジュールのプロジェクトへの参照をメインのアプリ側に追加して App.xaml.cs でモジュールを ModuleCatalog に追加します。

App.xaml.cs
using Prism.Ioc;
using Prism.Modularity;
using Prism.Unity;
using PrismHelloWorld.Main;
using PrismHelloWorld.Views;
using Windows.UI.Xaml;

namespace PrismHelloWorld
{
    sealed partial class App : PrismApplication
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override UIElement CreateShell() => Container.Resolve<Shell>();

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }

        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            moduleCatalog.AddModule<MainModule>();
        }
    }
}

実行してみると…

image.png

動いた!!

まとめ

とりあえずプロジェクトテンプレートで吐き出されるレベルのコードを作ってみただけですが、個人的に慣れ親しんだ WPF 版の Prism と同じ書き味で UWP のアプリ開発も出来るようになるのは、控えめに言って最高なので最高です。(語彙力の低下)

Prism 8.0 のリリースが楽しみですね。

今まで、UWP アプリ開発を楽にするためのフレームワークが個人的な決め手になるものが無かったのですが Prism が使えるなら割と積極的に使ってもいいかも。

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

【WPF】ItemsControlの基本的な使い方

はじめに

例えば

class Store
{
    public string Name { get; set; }
    public string Prefecture { get; set; }
    public int FavoriteCount { get; set; }
}

このようなクラスの List を画面上に表示し、一覧画面を作成したいというようなケースは多々あるかと思います。
言語によってはView上で for を使用しListをループすることで実現可能だったりしますが
残念ながらWPFではそのような方法では実現できません。

そこで登場するのがItemControlクラスです。
ItemsControlクラスはコレクションを並べて表示することができ、上記のようなことを実現するにはもってこいのクラスです。
また派生クラスにはListBoxクラスComboBoxクラスなどがあり、UIのカスタマイズなどを行うことができるようになっています。
非常に柔軟性が高く、お世話になる機会も多いだろうと思い、ItemsControlクラス(とその派生クラス)の基本的な使い方を整理していきます。

ソースコードについて

今回はMVVMでいうところのViewModelは以下を使用します。

MainViewModel.cs
namespace ItemsControl.ViewModels
{
    class MainViewModel
    {
        public MainViewModel()
        {
            this.Mall = Enumerable.Range(1 20).Select(x => new Store()
            {
                Name = "お店" + x,
                Prefecture = "東京都",
                FavoriteCount = x * 10,

            }).ToList();
        }

        public List<Store> Mall { get; set; }
    }
}

またModelとして前述のStoreクラスを使用しています。

ItemsControlでカスタマイズできること

ItemsControlでは4つのプロパティが用意されており、それらを設定することでカスタマイズしています。

プロパティ名 概要 設定で使用するコントロール
Template ItemsControlそのものを指定します。 ControlTemplate
ItemsPanel 要素の並べ方について指定します。 ItemsPanaleTemplate
ItemTemplate 要素を表示する際のテンプレートを指定します。 DataTemplate
ItemContainierStyle 要素に適応するStyleを指定します。 Style

それぞれの持ち場を図にするとこんな感じです。
ItemsControl.jpg

ではそれぞれ見ていきます。

Template

ControlTemplate を指定することで、コントロール全体の設定を行うことが多いです。

MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.Template>
        <ControlTemplate TargetType="{x:Type ListBox}">
            <Border BorderThickness="5" BorderBrush="Red" Background="LightGray">
                <ItemsPresenter Margin="5" />
            </Border>
        </ControlTemplate>
    </ListBox.Template>
</ListBox>

表示するとこんな感じ。
2020-04-26_14h23_42.png

見るとわかる通り、表示されているのはListに格納されているオブジェクトの名前になっています。
これはそのオブジェクト(今回の場合は Store クラス)に toString() が実装されていないためです。
なので toString() を実装してあげればこのままでもデータを表示することは可能です。

MainViewModel.cs
namespace ItemsControl.ViewModels
{
    class MainViewModel
    {
        public MainViewModel()
        {
            this.Mall = Enumerable.Range(1 20).Select(x => new Store()
            {
                Name = "お店" + x,
                Prefecture = "東京都",
                FavoriteCount = x * 10,

            }).ToList();
        }

        public List<Store> Mall { get; set; }

        public override string ToString()
        {
            return this.Name;
        }
    }
}

2020-04-26_14h25_44.png

ItemsPanel

ItemsPanelTemplate でコレクションをどう並べるかを指定します。
指定できるのはPanelクラスの派生クラスである以下の3つです。
なお、デフォルトで StackPanel が指定されているので、何も指定されていない場合は要素が縦に並びます。

クラス名 配置
StacPanel 縦に並ぶ
WrapPanel 横に並ぶ
Grid 指定はできるが子要素を並べる機能がないためすべて重なる
MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

表示するとこんな感じ。
2020-04-26_14h27_07.png

ちなみにGridで表示すると以下のようにすべての要素が重なります。
これ、どういうときに使うんだろうか...
2020-04-26_14h28_37.png

ItemTemplate

DataTemplate でコレクションの項目をどのように表示するかを指定します。
要は、各要素単位での表示設定です。

MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock>
                    <TextBlock.Text>
                        <MultiBinding StringFormat="【{0}】{1}">
                            <Binding Path="Prefecture" />
                            <Binding Path="Name" />
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
                <TextBlock Text="{Binding FavoriteCount, StringFormat=お気に入り:{0}}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

表示するとこんな感じ。
2020-04-26_14h30_54.png

ItemContainerStyle

Style を指定します。
ItemTemplate と同じく要素ごとの表示方法を指定するプロパティです。
要は、WebページでいうところのCSSです。

MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="OverridesDefaultStyle" Value="True" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ContentControl}">
                            <Border Background="{TemplateBinding Background}">
                                <ContentPresenter />
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                     <Setter Property="Background" Value="LightBlue"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="True">
                     <Setter Property="Background" Value="LightGreen" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

表示するとこんな感じ。
2020-04-26_14h45_26.png

Trigger では、特定の動作に連動させてStyleを変更することができます。
上記コードだと

  • マウスオーバーで要素の背景をLightBlueに
  • 選択した要素の背景をLightGreenに

という2点を対応しています。

最終的にできたもの

ここまで整理してきた4つのプロパティをすべて合わせたものがこちらです。
※見栄えを整えるために ItemContainerStyle に一部Styleを追加しています

MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
        <ListBox.Template>
        <ControlTemplate TargetType="{x:Type ListBox}">
            <Border BorderThickness="5" BorderBrush="Red" Background="LightGray">
                <ItemsPresenter Margin="5" />
            </Border>
        </ControlTemplate>
    </ListBox.Template>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding StringFormat="【{0}】{1}">
                                <Binding Path="Prefecture" />
                                <Binding Path="Name" />
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                    <TextBlock Text="{Binding FavoriteCount, StringFormat=お気に入り:{0}}" />
                </StackPanel>
            </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="OverridesDefaultStyle" Value="True" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ContentControl}">
                            <Border Background="{TemplateBinding Background}">
                                <ContentPresenter />
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <Setter Property="Margin" Value="10" />    <!-- 追加したStykle その1 -->
                <Setter Property="Width" Value="100" />    <!-- 追加したStykle その2 -->
                <Setter Property="Height" Value="50" />    <!-- 追加したStykle その3 -->
                <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                     <Setter Property="Background" Value="LightBlue"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="True">
                     <Setter Property="Background" Value="LightGreen" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

2020-04-26_14h54_29.png

まとめ

ItemsControlでコントロールの外見を変更する方法を整理しました。
変更のためには4つのパラメーターを変更していきます。
一覧系の画面や選択肢を列挙など、使用用途は幅広くお世話になることも多いかと思います。

なお、今回はListBoxを使用しましたが、基本的な考え方はComboBoxも同じなので流用できるはずです。

参考

WPF 実践
ItemsControl 攻略 ~ 外観のカスタマイズ

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

【Unity】シーンの事前ロード

環境

Unity 2019.3.7f1

はじめに

移動先のシーンが重くて遷移まで間が開いてしまう問題を解決するべく
シーンの事前ロードについて調べました。

方法

事前ロードし、ボタンを押したらシーン移動するコード

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;//追加

public class SceneTest : MonoBehaviour

{
    AsyncOperation a;//AsyncOperation型の変数aを宣言
    void Start()
    {
        //SceneManager.LoadSceneAsync("GameScene")の返り値(型はAsyncOperation)を変数aに代入
        a = SceneManager.LoadSceneAsync("GameScene");

        //AsyncOperationの中の変数allowSceneActivationをfalseにする
        //これがtrueになるとシーン移動する
        a.allowSceneActivation = false;
    }

    //ボタンに割り当て
    public void Change_Scene_button()
    {       
        //trueにしてシーン移動
         a.allowSceneActivation = true;
    }
}

解説

SceneManager.LoadSceneAsync("シーン名");のみを使ってシーン移動する場合、
下図のことが処理されています。
image.png

 
allowSceneActivationの情報を下図のように一旦falseにしておくことで
シーン読み込み後のシーン移動をストップします。
image.png

おわりに

ちょっとややこしいですが、定型文として覚えておく程度で良いと思います。
動けばOKの精神♪

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

プログラムの書き出しの話

最近、プログラムをやっていて思うのであれば、
自分が中学校の時に、初めて触ったプログラムはN80-BASICというやつなのであるが
いまでも覚えているが、下記の書き出しが頭の中に完全に入っていた。

WIDTH 80,25
CONSOLE 0,25,0,1
LOCATE 0,0
PRINT "HELLO WORLD"

もちろん、あの頃はこのコードを打ったところで、
インタプリタが無機質なカーソルを示すだけで、
何のヘルプもでないのであるが。。。

いわばプログラムの書き出しというやつは
今でも同じで、本当に無機質な奴だ。

さて、今日のお題ではあるが、
新しいプログラム言語、開発環境で始まるのは、
やはりプログラム君こんにちわのような、
挨拶が重要なのである。

別にプログラムの世界だけでなく、実際の世界でも
挨拶一つが通じれば、相手は何らかの話をしてくれるだろう。

もしも、何も分からずに、ただただ相手に近づいたとしても、
それこそ、チンプンカンプンとばかりに、困った顔をされるか、
酷い場合には、あっちへ行けと追い返されるだけである。

話が少々、脱線するが・・・

俺が始めて大学の卒業旅行に行ったとき、アメリカの空港のトランジットで
チケットを自分のキャスターケースに預けてしまって、
そして空港カウンターの黒人の女性に、何とか危機を説明したのであるが、
「ノーチケット!ノーライド!」
といって、本当に追い返されたことを、いまでも覚えている。

その後は、空港のヘルプデスクから日本語が使えると思しき
老人がやってきて、俺を荷物引き取り所まで連れて行ってくれた。

俺は自分の鞄の特徴が、銀色に近いものだったので
「ゴールド」といって、何とか鞄を引き戻して、
チケットを手にすることができた。

あの時の恐怖と、一緒に行った友達が、あざ笑っているのが
今でも覚えている、初めての海外旅行のエピソードである。
(懐かしい動画が、同窓会の時に話題になり、いまでもYOUTUBEには上げている)
https://www.youtube.com/watch?v=joamkkQdKQw

さて、そんな脱線話はさておいて、本題であるが、
つまりプログラム言語と言う奴は、一番最初に何を書くのか
という決まりごとは、いつになっても変わらず、
それを知っておかないと、まったく手も足も出せないのである。

そのうち、チャットボット側から、
「 お客さん何を書きましょうか? 」
などと親切にコードの出だしを、教えてくれないものだろうか?

せめてAIが出てきた暁には、本気でこのエンジニア不足と
コンピュータとのコミュニケーションのギャップを
埋めてくれることを期待するのであるが。

そんな訳で、今日のお題としては、最近習い始めている
プログラム言語の出だしについて、初学者でも分かるように
自分なりにまとめてみようと思う。

python ---> import

 pythonの場合は、import 文だ。
 これは間違っていないだろう。

C# ---> using

 C#の場合は、using節 だ。
別に演歌の出だしではない。

C --->  #include < >

ちょっと昔覚えたから、付け足しておく
c++ も基本同じかもしれないけど

node.js ---> require()

あえて、HTMLに突っ込まれるJavaScriptには言及しない

VB.NET ---> Imports

残念ながら、VBAにはこのようなお決まりは存在しないので
VB.NETの場合で書いておく。
 

JAVA ---> import

 個人的にJAVAは使ったことが無いので、知識として書き出しておく

その他

Typescript
RUBY
PHP
GO

いろいろあるんだろうけど。
いまの所は知らないでいる。

さてさて、こういう挨拶文というのは、
自分が相手に向かって、こんにちわ
という場合も、当然の如く必要であるが、
例えば、ちょっと他の人の会話を垣間見ようと
GITHUBなどを開いた場合でも同じである。

まず、この出だしの挨拶文から、
何もかもが始まるのであって、
ここから紐解いて行かないと
プログラムは完全に理解できないのである。

この挨拶文というのは、非常に重要であって、
例えるならば、相手の国のマナーや
しきたり、教養などの全般知識が詰め込まれている。

特に、最近は名前空間とか言ったりして、
(名前空間なんて名前つけなくて良くて)
そもそも、これは単なるツリー構造のディレクトリの
名前であって、逆にそういう風に言ってほしいぐらいである。

他にも、仮想空間とかいって、PYTHONではENVなどで
作られるのであるが、これも行ってしまえば、
単なる一時的に作られるフォルダの名前に過ぎないのである。
(本当に難しいことを、難しく言うのが、このITエンジニアの
嫌らしい決まりになっていて、だからこそ、回りくどくて
初学者を受け付けない、なんというか嫌らしい文化だ)

そして、最近のオブジェクト思考なんて言うのも、
昔は一人の職人が、自営で商売を始めたものを、
段々、多くの人間を雇って、組織的に働くようになって
部署をどんどん作っているようなものだ。

だいたいプログラムなんて言うのは、実際の世界のルールや
常識、決まりを、パソコンのストレージエリアに突っ込んでいるという
たんにそれだけの話なのに、どうも初学者を受け付けない
独特の雰囲気がある。

そんな訳で、自分はこれからの世の中が、IT社会になればなるほど、
これらの先端エンジニア、お宅エンジニアが我が物顔で
はびこる世の中に、風穴を開けてみたいのである。

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

ConsoleAppFramework + Serilog の実装例

.NET core コンソールアプリを運用していますが
いつのまにか機能を詰め込みすぎて、全体が把握できなくなり
機能追加が困難な時期がやってきました。

そこで、ConsoleAppFramework を導入してコードを整理しようと思います。

しかし、ログなどは Microsoft.Extensions の仕組みを使うようです。
残念ながら Microsoft.Extensions 関連は素人なので、
使い慣れたSerilogとの組み合わせ方法を探すのに苦労しました。
ようやく形になってきたので記録します。

(ConsoleAppFrameworkの機能の一部しか活用できていませんが、
 最初の一歩として参考になれば幸いです)

ConsoleAppFrameworkとは

ConsoleAppFramework - .NET Coreコンソールアプリ作成のためのマイクロフレームワーク(旧MicroBatchFramework)

慣れてしまうと普通のコンソールアプリには戻れませんね。

実装例

まずは結論からということでProgram.csとConsoleAppBaseの実装です。

Program.cs (全文)
using System;
using System.IO;
using System.Threading.Tasks;
using ConsoleAppFramework;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;

namespace ConsoleAppFrameworkSampleApp {
    public class Program {
        static async Task Main(string[] args) {
            Log.Logger = CreateLogger();
            try {
                await CreateHostBuilder(args).RunConsoleAppFrameworkAsync(args);
            } catch (Exception ex) {
                Log.Fatal(ex, "Host terminated unexpectedly");
            } finally {
                Log.CloseAndFlush();
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()
                .ConfigureServices((hostContext, services) => {
                    services.Configure<Settings>(
                        hostContext.Configuration.GetSection("Settings"));
                });

        public static ILogger CreateLogger() =>
            new LoggerConfiguration()
                .ReadFrom.Configuration(CreateBuilder().Build())
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .CreateLogger();

        public static IConfigurationBuilder CreateBuilder() =>
             new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
                .AddEnvironmentVariables();
    }
}
各機能の実装は ConsoleAppBase を継承して実装します
    public class Base1 : ConsoleAppBase {
        readonly ILogger logger;
        readonly Settings settings;

        public Base1(ILogger<Base1> logger, IOptions<Settings> settingsOp) {
            this.logger = logger;
            this.settings = settingsOp.Value;
        }

        public void Hello(
       [Option("n", "name of send user.")]string name,
       [Option("r", "repeat count.")]int repeat = 3) {
            for (int i = 0; i < repeat; i++) {
                this.logger.LogInformation("Hello My ConsoleApp Base1 from {name} {path}", name, this.settings.Path);
            }
        }
    }
exeを実行するときの引数で、実行するプログラムを指定します。
ConsoleAppFrameworkSampleApp.exe Base1.Hello -t TEST

以上で全部です。

https://github.com/panda728/ConsoleAppFrameworkSample/blob/master/README.md

以下で、各部を説明します。

Install-Packageについて

ログはSerilogを使いますので、とりあえず以下のものをインストール

Install-Package ConsoleAppFramework
Install-Package Serilog.Extensions.Logging
Install-Package Serilog.Extensions.Hosting
Install-Package Serilog.Settings.Configuration
Install-Package Serilog.Sinks.Console
Install-Package Serilog.Sinks.RollingFile
Install-Package Serilog.Enrichers.Environment
Install-Package Serilog.Enrichers.Process
Install-Package Serilog.Enrichers.Thread

Main部分

mainでは、ロガーを作成してConsoleAppFrameworkを起動します。

起動後は、ConsoleAppFrameworkが、引数に応じて適切なConsoleAppBaseクラスを実行してくれます。

なおロガーの細かい設定は後述

        static async Task Main(string[] args) {
            Log.Logger = CreateLogger();
            try {
                await CreateHostBuilder(args).RunConsoleAppFrameworkAsync(args);
            } catch (Exception ex) {
                Log.Fatal(ex, "Host terminated unexpectedly");
            } finally {
                Log.CloseAndFlush();
            }
        }

ロガーの設定について

ロガーの設定は appsettings.json にて行うスタイルにします。

appsettings.jsonから設定を読み込む処理は Serilog.Extensions.Hosting のSampleの書き方を参考にしました。

https://github.com/serilog/serilog-extensions-hosting/blob/dev/samples/SimpleServiceSample/Program.cs

        public static ILogger CreateLogger() =>
            new LoggerConfiguration()
                .ReadFrom.Configuration(CreateBuilder().Build())
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .CreateLogger();

        public static IConfigurationBuilder CreateBuilder() =>
             new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
                .AddEnvironmentVariables();

余談ですが appsettings.jsonについて。
テスト環境で「appsettings.Develogment.json」
本番環境で「appsettings.Production.json」
を優先して読み込むとのこと

Microsoft.Extensions関連の設定について

Host.CreateDefaultBuilderで設定している内容は
・Serilogの登録
・appsettings.jsonから設定情報を読み込む
 (設定情報は以下のようなSettingクラスを用意するスタイルです)

ここでservicesに設定したものは、後で作成するConsoleAppBaseのコンストラクタで受け取ることができます。
機能が増えて共通的に使いたいものができたら、ここでいろいろ増やせばよさそうです。

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()
                .ConfigureServices((hostContext, services) => {
                    services.Configure<Settings>(
                        hostContext.Configuration.GetSection("Settings"));
                });
(設定用クラスのサンプル)
    public class Settings {
        public string Path { get; set; }
        public string ConnectionString { get; set; }
    }

ConsoleAppBaseの実装例

上記のコードで準備が整いましたので
実際の処理部分を記述します。

ConsoleAppFrameworkのサンプルにあるHelloメソッド相当の機能に、ロガーと設定情報クラスを追加すると、以下の形になります。

    public class Base1 : ConsoleAppBase {
        readonly ILogger logger;
        readonly Settings settings;

        public Base1(ILogger<Base1> logger, IOptions<Settings> settingsOp) {
            this.logger = logger;
            this.settings = settingsOp.Value;
        }

        public void Hello(
       [Option("n", "name of send user.")]string name,
       [Option("r", "repeat count.")]int repeat = 3) {
            for (int i = 0; i < repeat; i++) {
                this.logger.LogInformation("Hello My ConsoleApp Base1 from {name} {path}", name, this.settings.Path);
            }
        }
    }

コンストラクタでロガーと設定情報クラスを受け取ることができますが
この辺りはMicrosoft.Extensionsのお仕事

DIがよくわからなかったのですが
 「コンストラクタで書いたものが勝手にセットされてくる」
と思ったら、便利さがわかったつもりになりましたw

appsetting.jsonの例

設定クラスとSerilogの設定は以下の要領で

{
  "Settings": {
    "Path": "c:\\temp",
    "ConnectionString": "(connection string)"
  },
  "Serilog": {
    "Using": [
      "Serilog.Sinks.Console",
      "Serilog.Sinks.RollingFile",
    ],
    "MinimumLevel": "Verbose",
    "WriteTo": [
      {
        "Name": "RollingFile",
        "Args": {
          "pathFormat": "logs\\log-{Date}.txt",
          "retainedFileCountLimit": "30"
        }
      },
    ],
    "Enrich": [ "WithMachineName", "WithProcessId", "WithThreadId" ],
    "Destructure": [
      {
        "Name": "ToMaximumDepth",
        "Args": { "maximumDestructuringDepth": 4 }
      },
      {
        "Name": "ToMaximumStringLength",
        "Args": { "maximumStringLength": 100 }
      },
      {
        "Name": "ToMaximumCollectionCount",
        "Args": { "maximumCollectionCount": 10 }
      }
    ]
  }
}

最後に

以前は引数で処理を分岐する部分を長々と記述していましたが
今後はConsoleAppFrameworkが引き受けてくれますのでProgram.csはシンプルに保てます。

そして、これまで苦労していた、
 ・巨大クラスで見通しが悪く、影響範囲を考慮しながら慎重に機能追加
 ・いつの間にか似たような処理を複数作っていた
といった時代とはおさらばできます。

新機能を追加したい場合は、新規にConsoleAppBaseを追加すれば、
従来の機能への影響を気にせず追加でき、共通機能はDIから受け取って使いまわせます。

クラスが増えても、コンストラクタを見れば
共通利用で使っているクラスが把握できるので安心です。

同じ処理のコードや、マスタデータが、複数クラスで必要になったら、DIに切り出す手が使えます。

なるべく見通しよいコード量でConsoleAppBaseを維持したいですね。

余談

appsettings.jsonの実行時パス問題が発生した場合は以下参照

How can I get my .NET Core 3 single file app to find the appsettings.json file?
https://stackoverflow.com/questions/58307558/how-can-i-get-my-net-core-3-single-file-app-to-find-the-appsettings-json-file

config.SetBasePath(GetBasePath());
config.AddJsonFile("appsettings.json", false);
private string GetBasePath()
{
    using var processModule = Process.GetCurrentProcess().MainModule;
    return Path.GetDirectoryName(processModule?.FileName);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む