20210109のC#に関する記事は7件です。

緯度経度と直交座標を相互変換する

はじめに

緯度経度高さで表される座標と、XYZの三次元直交座標を相互変換します。
Unityで使いたかったため、C#とUnityのタグをつけていますが、特にUnityに制限されるものではありません。

計算方法については、「世界測地系と座標変換」(飛田幹夫著、日本測量協会発行)を参考にしました。
世界測地系と座標変換(Amazon)
この本は日本測量協会に問い合わせれば定価で購入できると思います。

※注意※
著者は本稿の内容について、精度や正確さ正しさを必ずしも保証するものではありません。
著者は本稿の内容を利用したことにより起こった事柄に関して一切の責任を負わないものとします。
実際に使われる場合は、値域の制限などを入れることや、精度保証などのテストを行って、ご自身の責任においてご利用ください。

前提

緯度経度はWGS84に基づいているものとします。変換パラメータを変えれば、他の測地系にも対応できると思いますが、本稿の範囲外とします。
緯度は北側が+、南側に-で、-90~90度。経度は東に+、西に-で、-180~180度とします。
高さは、WGS84に基づく楕円体高でメートルを単位とします。標高ではないのでご注意ください。
XYZ座標は、+X軸が子午線、東向きに+Y軸、北向きに+Z軸とします。こちらも単位はメートルになります。

プログラム

CoordConv.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System;

/// <summary>
/// 座標の変換用クラス。なるべくシンプルに必要な変換だけ。
/// 測地系はWGS84を使用。
/// 計算方法は「世界測地系と座標変換 飛田幹夫 日本測量協会」を参考にした。
/// </summary>
public class CoordConv
{
    private const double a = 6378137.0;
    private const double f = 1.0 / 298.257223563;
    private const double e2 = f * (2.0 - f);

    /// <summary>
    /// 緯度経度(WGS84)から直交座標(ECEF)に変換するメソッド。
    /// </summary>
    /// <param name="b">緯度</param>
    /// <param name="l">経度</param>
    /// <param name="h">楕円体高</param>
    /// <returns>直交座標のXYZ。+Zが北極、+Xが子午線、+Yが東経方向</returns>
    public static (double x, double y, double z) BLH2XYZ(double b, double l, double h){
        b = Math.PI * b / 180.0;
        l = Math.PI * l / 180.0;

        double N = a / Math.Sqrt(1.0 - e2 * Math.Pow(Math.Sin(b),2.0));

        return (
            (N + h) * Math.Cos(b) * Math.Cos(l),
            (N + h) * Math.Cos(b) * Math.Sin(l),
            (N * (1.0 - e2) + h) * Math.Sin(b) 
        );
    }

    /// <summary>
    /// 直交座標(ECEF)から緯度経度(WGS84)に変換するメソッド。
    /// </summary>
    /// <param name="x">X</param>
    /// <param name="y">Y</param>
    /// <param name="z">Z</param>
    /// <returns>緯度経度。b緯度、l経度、h楕円体高</returns>
    public static (double b, double l, double h) XYZ2BLH(double x, double y, double z){
        double p = Math.Sqrt(x*x + y*y);
        double r = Math.Sqrt(p*p + z*z);
        double mu = Math.Atan(z / p * ((1.0 - f) + e2 * a/r));

        double B = Math.Atan( (z * (1.0-f) + e2*a*Math.Pow(Math.Sin(mu),3)) / ((1.0-f)*(p-e2*a*Math.Pow(Math.Cos(mu),3))) );
        return (
            180.0 * B / Math.PI,
            180.0 * Math.Atan2(y,x) / Math.PI,
            p * Math.Cos(B) + z*Math.Sin(B) - a*Math.Sqrt(1.0 - e2*Math.Pow(Math.Sin(B),2))
        );
    }
}

注意点

必ず、doubleで値を渡してください。floatでは一般的な用途でも演算の精度が不足する可能性が高いです。得られた値は、なるべく小さな値になるようにdoubleのまま平行移動などしてからfloatに変換し、Unityのオブジェクトの座標(Vector3など)に代入してください。
計算部分では値域のチェックなどもしていません。必要なら追加してください。
変換パラメータがWGS84の改訂などで変わることがあります。高い精度を求める場合は都度信頼できる情報を確認してください。

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

[WPF] ハンバーガーメニュー(SplitView)を使う

■概要

SplitViewを使用してハンバーガーメニューを実現する。
000_.png

■準備

?ここ を参考に「2. プロジェクト作成」から「5. テーマ適用」まで実施する。

※「2. プロジェクト作成」でプロジェクト名はHamburgerSample、フレームワークは.NET 5.0にした。
※「4. NuGet」は「ModernWpfUI」だけ入れればOK、「ModernWpf.MessageBox」は今回のサンプルでは使っていない。

■コンテンツ部品 追加

ソリューションエクスプローラーでプロジェクト直下に「追加」-「ユーザーコントロール (WPF)」でUserControl1.xamlを追加する。
100.png

TextBlockを1つ追加。

UserControl1.xaml
<UserControl
    x:Class="HamburgerSample.UserControl1"
    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:local="clr-namespace:HamburgerSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <Grid>
        <TextBlock
            x:Name="TxtContent"
            Padding="12"
            FontFamily="UD Digi Kyokasho NK-R"
            FontSize="22"
            TextWrapping="Wrap">
            ニャー、ニャーと試みにやって見たが誰も来ない。そのうち池の上をさらさらと風が渡って日が暮れかかる。腹が非常に減って来た。泣きたくても声が出ない。仕方がない、何でもよいから食物のある所まであるこうと決心をしてそろりそろりと池を左りに廻り始めた。どうも非常に苦しい。そこを我慢して無理やりに這って行くとようやくの事で何となく人間臭い所へ出た。ここへ這入ったら、どうにかなると思って竹垣の崩れた穴から、とある邸内にもぐり込んだ。縁は不思議なもので、もしこの竹垣が破れていなかったなら、吾輩はついに路傍に餓死したかも知れんのである。一樹の蔭とはよく云ったものだ。この垣根の穴は今日に至るまで吾輩が隣家の三毛を訪問する時の通路になっている。さて邸へは忍び込んだもののこれから先どうして善いか分らない。
        </TextBlock>
    </Grid>
</UserControl>

■メイン画面作成

ResizeMode="CanResizeWithGrip"追加。
Gridを2行に分割、1行目はハンバーガーボタン。

MainWindow.xaml
<Window
    x:Class="HamburgerSample.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:local="clr-namespace:HamburgerSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ui="http://schemas.modernwpf.com/2019"
    Title="MainWindow"
    Width="800"
    Height="450"
    ui:WindowHelper.UseModernWindowStyle="True"
    ResizeMode="CanResizeWithGrip"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal">
            <ToggleButton x:Name="HamburgerButton" Click="HamburgerButton_Click">
                <ui:SymbolIcon Symbol="GlobalNavigationButton" />
            </ToggleButton>
            <TextBlock
                Margin="6,0"
                VerticalAlignment="Center"
                FontSize="18"
                Text="タイトル" />
        </StackPanel>

        <ui:SplitView
            x:Name="Menu"
            Grid.Row="1"
            DisplayMode="Overlay"
            OpenPaneLength="200"
            PaneBackground="{DynamicResource SystemControlPageBackgroundChromeMediumLowBrush}"
            PaneClosed="Menu_PaneClosed">
            <ui:SplitView.Pane>
                <ui:SimpleStackPanel Margin="6" Spacing="6">
                    <ui:SimpleStackPanel.Resources>
                        <!--  ボタン(メニュー項目)のスタイル  -->
                        <Style BasedOn="{StaticResource DefaultButtonStyle}" TargetType="Button">
                            <Setter Property="HorizontalAlignment" Value="Stretch" />
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                            <EventSetter Event="Click" Handler="FontSizeButton_Click" />
                        </Style>
                    </ui:SimpleStackPanel.Resources>

                    <Button Tag="10">
                        <StackPanel Orientation="Horizontal">
                            <ui:SymbolIcon Symbol="Font" />
                            <TextBlock Text="文字サイズ - 小" />
                        </StackPanel>
                    </Button>
                    <Button Tag="22">
                        <StackPanel Orientation="Horizontal">
                            <ui:SymbolIcon Symbol="Font" />
                            <TextBlock Text="文字サイズ - 中" />
                        </StackPanel>
                    </Button>
                    <Button Tag="36">
                        <StackPanel Orientation="Horizontal">
                            <ui:SymbolIcon Symbol="Font" />
                            <TextBlock Text="文字サイズ - 大" />
                        </StackPanel>
                    </Button>
                </ui:SimpleStackPanel>
            </ui:SplitView.Pane>

            <!--  メインコンテンツ  -->
            <ui:SplitView.Content>
                <ScrollViewer Margin="0,0,0,12">
                    <local:UserControl1 x:Name="MainContent" />
                </ScrollViewer>
            </ui:SplitView.Content>
        </ui:SplitView>
    </Grid>
</Window>

■メイン画面ロジック作成

MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls.Primitives;
using ui = ModernWpf.Controls;

namespace HamburgerSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// メニューが閉じたとき
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <remarks>
        /// ハンバーガーボタンがOFFにされたりメインコンテンツ部分を触ったとき
        /// </remarks>
        private void Menu_PaneClosed(ui.SplitView sender, object args)
        {
            HamburgerButton.IsChecked = false;
        }

        /// <summary>
        /// ハンバーガーボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HamburgerButton_Click(object sender, RoutedEventArgs e)
        {
            // ハンバーガーボタンがONの状態ならメニューを開く
            Menu.IsPaneOpen = (sender as ToggleButton)?.IsChecked == true;
        }

        /// <summary>
        /// 文字サイズ変更ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FontSizeButton_Click(object sender, RoutedEventArgs e)
        {
            // Tagに設定された文字サイズをメインコンテンツのテキストに設定
            MainContent.TxtContent.FontSize =
                double.Parse((sender as FrameworkElement).Tag.ToString());
        }

    }
}

■実行

110.png

ハンバーガーボタンをクリック、コンテンツの上にメニューが表示される。
120.png

130.png

ライトテーマ
140.png

■参考

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

C#で塗りつぶし描画アプリを作る

以前、ピクチャボックス内をマーカーでベタ塗りするアプリを作った。
それを改良して、ボタンを押した時だけピクチャボックスが塗りつぶされるアプリを作成する。

追加仕様
・ピクチャボックス内をマーカーが移動する
・保存ボタンを押すと、マーカーが表示される位置が塗りつぶされる。
・塗りつぶされた部分は消えない。

C#&アプリ初心者の練習です。

アプリの作成

環境:Windows+VisualStudio(Winforms)

drawpolygon.cs
    public partial class Form1 : Form
    {
        int SaveTrigger = 0;
        List<PointF[]> list = new List<PointF[]>();

        public Form1()
        {
            InitializeComponent();           
        }

        private void PastePicture(object sender, PaintEventArgs e)
        {
            int mouseY = Decimal.ToInt32(numericUpDown1.Value);

            SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(88, 0, 0, 255));
            Brush brush = new SolidBrush(Color.Gray);

            PointF point1 = new PointF(0, mouseY + 100);
            PointF point2 = new PointF(pictureBox1.Width, mouseY + 100);
            PointF point3 = new PointF(pictureBox1.Width, mouseY - 100);
            PointF point4 = new PointF(0, mouseY - 100);

            PointF[] curvePoints =
            {
                 point1,
                 point2,
                 point3,
                 point4
            };

            // 保存が押された時の座標を保存
            if(SaveTrigger == 1)
            { 
                list.Add(curvePoints);
                SaveTrigger = 0;
            }

            for (int i = 0; i < list.Count; i++)
            {
                e.Graphics.FillPolygon(brush, list[i]);
            }

            // 常にマーカーを描画
            e.Graphics.FillPolygon(semiTransBrush, curvePoints);
        }

        private void numericUpDown1_ValueChanged(object sender, EventArgs e)
        {
            pictureBox1.Invalidate();
        }

        private void save_Click(object sender, EventArgs e)
        {
            pictureBox1.Invalidate();
            SaveTrigger = 1;
        }
    }

Paintイベント内に「マーカーの描画」と「塗りつぶし部分の描画」をそれぞれ作成した。
「マーカーの描画」は、数値入力ボックスの値を座標とする。
「塗りつぶし部分の描画」は、保存ボタンが押された時だけ実行される。
(Paintイベントにパラメータ情報を渡す方法がわからず、保存ボタンが押された時のみ実行するかどうかの判定にSaveTriggerというグローバル変数を使ってしまった。良い方法があれば教えて下さい。)

実行画面
2021-01-09_18h46_52.gif

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

【.net、Java連携】C#.netで.jarを実行するンジャー

どうしてもJavaを使わないとできない処理をC#.netから起動する必要が出た場合、
その実装に困りますよね…
そこで参上!C#.netで.jarを実行するンジャー!!(ここまで茶番)

Java側のプログラム

java側のプログラムはmainメソッド(関数)を入れます。
main関数のコマンドライン引数"args"でC#から値を受け取ります。
下記サンプルプログラムでは、1つの引数でメッセージ:msgを受け取り、それを表示しています。

Sample.java
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        if (args.length <= 0) {
            System.out.println("出力メッセージなし");
        } else {
            System.out.println("出力メッセージ:" + args[0]);
        }
        // Enterキー入力待ち
        // 参考:https://stackoverflow.com/questions/26184409/java-console-prompt-for-enter-input-before-moving-on
        System.out.println("Press \"ENTER\" to exit...");
        Scanner scanner = new Scanner(System.in);
        scanner.nextLine();
    }

プログラムができたら、実行可能な.jarファイルを生成しましょう。

C#(.net)側のプログラム

C#側では、Processクラスのオブジェクトで.jarファイルを起動します。
.jarファイルは、C#(.net)の実行ファイルが生成されるDebug/Releaseフォルダに配置するとファイル指定が楽です。

// コマンドプロンプトを表示して実行する場合
Process.Start("java", "-jar (.jarファイル名orパス) (引数) (引数)…"))
// コマンドプロンプトを表せずして実行する場合
Process.Start("javaw", "-jar (.jarファイル名orパス) (引数) (引数)…"))

.jarファイルの実行中にC#のプログラムを止めたい場合、WaitForExitメソッドを使いましょう。

// 終了待ち
jar.WaitForExit();

.jarファイルの実行結果は、ExitCodeメソッドで受け取れます。
※javaのmainメソッドが void main() なら、戻り値0で正常終了

// 結果取得(0:正常終了)
jar.ExitCode();

オブジェクトを作成しますので、usingやclose,disposeなどによるオブジェクト解放をお忘れなく。
参考:確保したリソースを忘れずに解放するには?[C#/VB]
実装例↓

ConnectJar.cs
        /// <summary>
        /// .jar実行
        /// </summary>
        /// <param name="msg">メッセージ</param>
        /// <returns>True:成功/False:失敗</returns>
        public static bool Excecute(string msg)
        {
            bool result = false;
            Process jar = null;
            try
            {
                // .jarをプロセスとして起動
                using (jar = Process.Start("java", "-jar Sample.jar " + msg))
                {
                    // 終了待ち
                    jar.WaitForExit();
                    // 結果取得(0:正常終了)
                    if (jar.ExitCode == 0) result = true;
                }
            }
            catch (Exception e)
            {
                MessageBox.Show("例外発生\n" + e.Message);
            }
            return result;
        }

あとはC#.netを実行するだけ…!
サンプルコード全体は下記リポジトリにあります。
CSJarソースコード

補足

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

[.net、Java連携]C#.netで.jarを実行するンジャー

どうしてもJavaを使わないとできない処理をC#.netから起動する必要が出た場合、
その実装に困りますよね…
そこで参上!C#.netで.jarを実行するンジャー!!(ここまで茶番)

Java側のプログラム

java側のプログラムはmainメソッド(関数)を入れます。
main関数のコマンドライン引数"args"でC#から値を受け取ります。
下記サンプルプログラムでは、1つの引数でメッセージ:msgを受け取り、それを表示しています。

Sample.java
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        if (args.length <= 0) {
            System.out.println("出力メッセージなし");
        } else {
            System.out.println("出力メッセージ:" + args[0]);
        }
        // Enterキー入力待ち
        // 参考:https://stackoverflow.com/questions/26184409/java-console-prompt-for-enter-input-before-moving-on
        System.out.println("Press \"ENTER\" to exit...");
        Scanner scanner = new Scanner(System.in);
        scanner.nextLine();
    }

プログラムができたら、実行可能な.jarファイルを生成しましょう。

C#(.net)側のプログラム

C#側では、Processクラスのオブジェクトで.jarファイルを起動します。
.jarファイルは、C#(.net)の実行ファイルが生成されるDebug/Releaseフォルダに配置するとファイル指定が楽です。

// コマンドプロンプトを表示して実行する場合
Process.Start("java", "-jar (.jarファイル名orパス) (引数) (引数)…"))
// コマンドプロンプトを表示せずに実行する場合
Process.Start("javaw", "-jar (.jarファイル名orパス) (引数) (引数)…"))

.jarファイルの実行中にC#のプログラムを止めたい場合、WaitForExitメソッドを使いましょう。

// 終了待ち
jar.WaitForExit();

.jarファイルの実行結果は、ExitCodeメソッドで受け取れます。
※javaのmainメソッドが void main() なら、戻り値0で正常終了

// 結果取得(0:正常終了)
jar.ExitCode();

オブジェクトを作成しますので、usingやclose,disposeなどによるオブジェクト解放をお忘れなく。
参考:確保したリソースを忘れずに解放するには?[C#/VB]
実装例↓

ConnectJar.cs
        /// <summary>
        /// .jar実行
        /// </summary>
        /// <param name="msg">メッセージ</param>
        /// <returns>True:成功/False:失敗</returns>
        public static bool Excecute(string msg)
        {
            bool result = false;
            Process jar = null;
            try
            {
                // .jarをプロセスとして起動
                using (jar = Process.Start("java", "-jar Sample.jar " + msg))
                {
                    // 終了待ち
                    jar.WaitForExit();
                    // 結果取得(0:正常終了)
                    if (jar.ExitCode == 0) result = true;
                }
            }
            catch (Exception e)
            {
                MessageBox.Show("例外発生\n" + e.Message);
            }
            return result;
        }

あとはC#.netを実行するだけ…!
サンプルコード全体は下記リポジトリにあります。
CSJarソースコード

補足

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

Blazor Tutorialまとめ(番外×2 + EP10~)

本家動画はこちら
EP01~05はこちら
EP06~09はこちら

EP10でREST API扱うんだけど、API叩くにもちょうどいいのがないので、この人の別動画
 ASP.NET Core Web API + Entity Framework Core
の基本を番外でまとめていくよ。

番外①:REST APIの作り方

  • ASP.NET CoreでREST APIを作るのは簡単だよ。
  • まずは専用のプロジェクトを作るパターンから見てみよう。
  • プロジェクトを新規作成して「ASP.NET Core Webアプリケーション」を選択するよ。プロジェクト名は何でもいいよ。
  • 次に「API」を選択してプロジェクトを作成するよ。するとサンプル付きでプロジェクトができるよ。
  • とりあえずこのまま何も変更せずに実行してみよう。すると「https://localhost:(ポート番号)/weatherforecast」に遷移してJSONが返ってきたね。
  • それじゃコードについて説明していこう。
  • まずは「Controllers」フォルダ内の「WeatherForecastController.cs」。これがデータを返してる本体だよ。
  • クラス名が「WeatherForecastController」ってなってるけど、このクラス名のControllerより前の部分[Route("[controller]")][controller]に該当するよ。試しにクラス名を「HogeController」に変えて実行してみると、初期遷移の「https://localhost:(ポート番号)/weatherforecast」はエラーになって「https://localhost:(ポート番号)/hoge」にアクセスするとJSONが返ってくるのがわかるね。
  • どのメソッドがJSONを返しているかというと[HttpGet]属性がついたIEnumerable<WeatherForecast> Get()が返してるよ。このメソッドみたいに[HttpGet]属性つけてIEnumerable<(任意の型)>ってやれば勝手にクラスをJSONに変換して返してくれるんだね。
  • ちなみにこのAPIはBlazorとかASP.NET MVCとかにも追加できるよ。
  • ただその場合、「Startup.cs」の編集が必要で、ConfigureServicesメソッドにservices.AddControllers();の追加と、Configureメソッドにapp.UseHttpsRedirection();app.UseAuthorization();endpoints.MapControllers(); の追加が必要だよ。細かい追加位置はサンプルを確認してね。

(あとはこの人が公開してるサンプル(CuriousDrive/BookStores - GitHubの「SalesController.cs」)見たほうが早いかも )
(↑のサンプルよく見ずにVisualStudioが作るテンプレベースにしてたらハマった・・・)
(VisualStudioが作るテンプレだとAPIの戻り値がIEnumerable<WeatherForecast>になってるけど、これだとGetFromJsonAsyncで戻り値が取れん。APIの定義側も非同期に対応するためにTask<ActionResult<IEnumerable<WeatherForecast>>ってせんとあかんかった)
(EntityFrameworkCoreと組み合わせたらもっと簡単な方法があった…(´・ω・`))
(DBコンテキスト継承したクラス作ってEntityFrameworkCoreで読み書きできる状態にした上で、プロジェクト内の任意のフォルダ右クリック→追加→コントーローラー→共通→API→Entity Frameworkを使用したアクションがあるAPIコントローラー、で一発で参照/追加/削除のWeb APIが生成された…)
(↑で生成したコントローラー、なんかうまく動かん…)
(「Startup.cs」の public void ConfigureServices(IServiceCollection services) の中に services.AddScoped<AuthorContext>(); を追加する必要があったわ。)
(クラスにバリデーションつけとけば勝手に文字列長とかチェックしてエラー返してくれるのイイね)
(ただやっぱり勉強用コードとは言え、DBエラーとか例外はちゃんとcatchしてエラーメッセージ返さないとエラー発生時が大変だわ)

番外②:EntityFrameworkCore

(動画は長いしSQLServer立てる必要もあるからSQLite使って今までの書き方みたく書いていこう。参考にしたのは
 Entity FrameworkでSQLiteを利用する方法とは とか
 Database.EnsureCreatedおよびDatabase.Migrateを呼び出す方法と場所 とか
  Creating a Model for an Existing Database in Entity Framework Core とか
 VSCodeでRESTクライアントを使って効率的にAPIを呼び出す。 とか
 C# EntityFrameworkでIDENTITY属性を無効化する とか。)

  • EntityFrameworkを使って、今まで使ってたAuthorクラスのデータをデータベースに保存してみよう。
  • RDBMSはインストールとかいらないSQliteを使うよ。
  • SQliteはRDBMSの一種でライブラリだけで動いてファイル単位でDBを持てるやつだよ。
  • 今までのプロジェクトに追加してもいいけど、わかりやすいようにコンソールアプリのプロジェクトを別で作ろう。
  • プロジェクトを作ったらAuthorクラスのソースをこのプロジェクトに追加しよう。
  • AuthorクラスのAuthorIdには[Key]属性を付与しよう。こうすることでプライマリキーになって自動採番(SQLiteならAUTOINCREMENT)されるようになるよ。
  • ちなみに自動採番したくない場合は追加で[DatabaseGenerated(DatabaseGeneratedOption.None)]を属性追加すればいいよ。
  • 次にNugetで Microsoft.EntityFrameworkCore.Sqlite を追加するよ。
  • 続いてコードを修正するよ。using System.ComponentModel.DataAnnotations.Schema;Author の前に [Table("Authors")] を追加するだけだよ。
Author.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;//←追加

namespace EntityFrameworkTest {
    [Table("Authors")]//←追加
    public class Author {
  • 続いて DbContext を継承したクラスを作るよ。
AuthorContext.cs
public class AuthorContext : DbContext {
    public DbSet<Author> Authors { get; internal set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder){
        var connectionString = new SqliteConnectionStringBuilder { DataSource = @"C:\tmp\sample.db" }.ToString();
        optionBuilder.UseSqlite(new SqliteConnection(connectionString));
    }
}
  • これで準備はOK。DBを読み書きしてみよう。
Program.cs
static void Main(string[] args) {
    // コンテキスト・クラスを作成
    using (var db = new AuthorContext()) {
        // スキーマとかテーブルが無かったら作るよ。
        db.Database.EnsureCreated();

        //サンプルに山田さんを100人
        for (var j = 0; j < 100; j++) {
            //AuthorIdは自動採番されるので未指定でいいよ
            db.Authors.Add(new Author
                            {
                                FirstName = "太郎",
                                LastName = "山田",
                                City = "東京",
                                EmailAddress = "taro@tokyo.com",
                                Salary = 10000000,
                                Phone = "03-1234-4567"
                            };);
        }

        // DBへ保存
        int recordsAffected = db.SaveChanges();

        // 順に取り出して表示
        foreach (var i in db.Authors) {
            Console.WriteLine("AuthorId: {0} , FirstName: {1} , LastName: {2}", i.AuthorId, i.FirstName, i.LastName);
        }

        // キー入力待ち
        Console.ReadKey();
    }
}
  • ホントにDB読み書きしてるのか不安になるレベルのコードの少なさだけど、一回実行した後 EnsureCreated から SaveChanges までをコメントアウトしてもちゃんとデータが表示できてるから、ちゃんとDB読み書きしてることがわかるね。
  • コードからDBを作ったけど、今度はDBからコードを作ってみよう。
  • まずは別プロジェクトのコンソールアプリで 「EntryFrameworkTestFromDB」 っていうのを作ってみよう。
  • NuGetで Microsoft.EntityFrameworkCore.SqliteMicrosoft.EntityFrameworkCore.Tools を追加するよ。
  • パッケージマネージャコンソール(表示されてなければメニューの「表示」→「その他のウィンドウ」→「パッケージマネージャコンソール」から表示できるよ)にコマンド Scaffold-DbContext "DataSource=C:\tmp\sample.db;" Microsoft.EntityFrameworkCore.Sqlite を実行してみよう。
  • Author.cs」「sampleContext.cs」が生成されたね。
  • sampleContext.cs」だと名前がいまいちなので、名前は変えておこう。
  • sampleContext.cs」を開いてクラス名 sampleContext を右クリック、「名前の変更」を選択しよう。
  • sampleContext がハイライトされるのでクラス名を AuthorContext に変更しよう。
  • 「シンボルのファイル名を変更する」にチェックが入っていることを確認して「適用」をクリックするとファイル名も含めてプロジェクト内の sampleContextAuthorContext に置き換わるよ。
  • 同ファイルにはプリプロセッサ#warningで「ソースコードの中に接続文字列を入れないようにしようね」みたいな警告が出るけど、今回は無視するので削除しておこう。
  • あとはメイン関数に「EntryFrameworkTest」で書いた表示プログラムを張り付けてみたらちゃんとデータが表示されることがわかるね。

EP10:REST APIの呼び出し

  • 今回はREST APIの呼び出し方について説明するよ。
  • 手順としては@inject HttpClientを追加してGetJsonAsyncGetFromJsonAsync<T>PostJsonAsyncPostAsJsonAsyncPostAsyncDeleteAsyncPutJsonAsyncPutAsJsonAsyncPutJsonAsyncでREST APIを叩くだけだよ。

(動画ではGetJsonAsyncPostJsonAsyncPutJsonAsyncやったけど、.NET5でやってたらGetFromJsonAsyncPostAsJsonAsyncPutAsJsonAsyncやった。名前変わったんかな?)
(あと日本語とか含んでると文字のエンコード処理がうまくいかないからPostJsonAsyncPostJsonAsyncは使えんかった。)

  • 別の動画で作った本屋さんWebAPIを使って実際にやってみよう。
  • まずは「Startup.cs」のConfigureServicesservices.AddSingleton<HttpClient>();を追加するよ。
  • 続いて使いたいページに@inject HttpClient Http;を追加するよ。
  • あとはGetFromJsonAsync<T>で指定したURLを叩くだけだよ。

(「_Imports.razor」に@using System.Net.Http.Json追加せな「GetFromJsonAsyncとか知らんで」って言われた。)
(無料の天気予報REST APIで試そうかとも思ったけど、これ用で作った方がシンプルかなぁ)
(→番外作成。地味にハマった・・・)

  • 今回の場合Authorのリストを取得したいからGetFromJsonAsync<LIST<Author>>となるね。
  • JSONのキー名をそのままクラスのプロパティ名にしたクラスを作っておけば後は勝手に変換してくれるよ。
  • あとこの実行は非同期だからnull参照して落ちないように戻り値用変数はnew List<Author>で初期化しておく必要があるね。

(自分で試したときは一々別プロジェクト立ち上げるのが面倒だったので、同じプロジェクトにコントローラー追加した。なので呼び出しは↓みたいな感じに。)

List<Product> ret = new List<Product>;
ret = await Http.GetFromJsonAsync<List<Product>>(NavigationManager.BaseUri + "Values");
  • あと取ったデータはauthirKust.OrderByDescending(auth => auth.AuthorId).ToList<Author>();で降順に並べておこう。
  • それじゃ実行してみよう。ちゃんとAPIで取ってきたデータを表示しているのがわかるね。
  • 続いて値の設定に移ろう。
  • SaveAuthor 関数のところでPOSTするようにするよ。(動画ではPostAsJsonAsync使ってるけど、日本語含んでるとエンコードがうまくいかないのかエラーになるのでPostAsync使わないとダメそう?あと、動画では書いてないけど、POSTの結果でJSONが返ってくる場合はReadFromJsonAsyncで結果を読んでデシリアライズが一発できるみたい)
var json = JsonSerializer.Serialize(x, new JsonSerializerOptions {
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
});
var content = new StringContent(json, Encoding.UTF8, "application/json");
var ret = await Http.PostAsync(NavigationManager.BaseUri + "api/Authors", content);
var response = await ret.Content.ReadFromJsonAsync<Author>();
  • 実行してみると正常にデータは更新されているはずなのに表示が更新されないのがわかるね。
  • これはデータを取り直して StateHasChanged() で画面に対して更新通知を行っていないからだよ。
  • POSTした後にデータを取り直して StateHasChanged() を呼んでみよう。
  • 実行してみるとちゃんとデータが更新されることがわかるね。
  • 次は削除と更新をしてみよう。
    (動画じゃ分けてるけどPUTとDELETEの違い程度なので一緒で。)

  • まずは表の各行に「更新」と「削除」ボタンを付けるよ。
    (動画じゃHTMLタグにスタイル直書きしてるけどイマイチだなぁ…せっかくだしSCSS試してみるか。それより先にBootstrap覚えよ)
    とほほのBootstrap 4入門
    (杜甫々さんのページ、HP作るのにHTML覚えた(1998年頃)以来に見るけどすごいコンテンツ増えてるなぁ)
    (Bootstrap使うのはテーブルボタンかなぁ。動画だとハイパーリンクだけどボタンにしとこ)

<table class="table table-striped">
    <thead class="thead-lignt">
        <tr>
            <th>ID</th>
            <th>First name</th>
            <th>Last name</th>
            <th>City</th>
            <th>Email</th>
            <th>更新/削除</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var a in author_list)
        {
            <tr>
                <td>
                    @a.AuthorId
                </td>
                <td>
                    @a.FirstName
                </td>
                <td>
                    @a.LastName
                </td>
                <td>
                    @a.City
                </td>
                <td>
                    @a.EmailAddress
                </td>
                <td>
                    <button type="button" class="btn btn-outline-primary btn-sm" role="button" @onclick="(() => EditAuthor(a.AuthorId, author))">Edit</button> |
                    <button type="button"  class="btn btn-outline-danger btn-sm" role="button" @onclick="(() => DeleteAuthor(a.AuthorId))">Delete</button>
                </td>
            </tr>
        }
    </tbody>
</table>
  • 続いて更新と削除のコードを書くよ。表示更新はどっちも同じなので関数化したよ。
private async Task UpdateTable() {
    author_list = await Http.GetFromJsonAsync<List<Author>>(NavigationManager.BaseUri + "api/Authors");
    StateHasChanged();
}

public async Task EditAuthor(int _id, Author _author) {
    _author.AuthorId = _id;
    var json = JsonSerializer.Serialize(author, new JsonSerializerOptions {
        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
    });
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    var ret = await Http.PutAsync(NavigationManager.BaseUri + "api/Authors/" + _id.ToString(), content);
    if (ret.IsSuccessStatusCode) {
        //今は結果を返さないのでひとまずコメントアウト
        //var response = await ret.Content.ReadFromJsonAsync<Author>();
    }

    await UpdateTable();
}

public async Task DeleteAuthor(int _id)
{
    var ret = await Http.DeleteAsync(NavigationManager.BaseUri + "api/Authors/" + _id.ToString());
    await UpdateTable();
}

(動画じゃSaveAuthorにif文追加してPOSTとPut分岐してるな。しかも動作は「Edit」押したらフォームに値設定→Saveで更新 か、上記だとフォームに入力→Edit押したら更新になるな。けどまぁRESTの動き見るだけだからまぁいっか)

EP11:Authentication | Out of the box

EP12:Authentication | Custom AuthenticationStateProvider

EP13:Layouts | Login Pages

EP14:HttpClient | Login User

EP15:IHttpClientFactory | Login User

EP16:Sending JWT token & Building Request Middleware

EP17:Register User & Generate JWT

EP18:Role-based Authorization

EP19:Policy-based Authorization

EP20:Procedural Logic | Authentication & Authorization in C

EP21:Templated Components | Html Table

EP22:Razor Components | EventCallback

EP23:Event Handling

EP24:GridView Header Filter

EP25:Gridview Paging

EP26:Spinner or Activity Indicator : EP26

EP27:Code Faster Using dotnet watch run

EP28:Deploy to IIS

EP29:Deploy to Azure App Services

EP30:Handling Exceptions

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

スクリプト1つでシーンの暗転切り替え

概要

UniTaskがversion2からDOTweenのawaitにも対応したと聞いたので、作ってみました。
締め切りまで時間がない!そんな時にスクリプト1つ付けるだけでひとまず暗転遷移ができます。

必要なライブラリ

  • UniTask
  • DOTween

ただし、動作させるためには次の設定が必要だそうです。

OpenUPMからDOTweenを導入する、またはScripting Define Symbolsに「UNITASK_DOTWEEN_SUPPORT」を定義する
assembly definition「UniTask.DOTweeen」への参照を追加する

引用:https://qiita.com/toRisouP/items/8f66fd952eaffeaf3107

今回は以下の3つをasmdefに登録しました。
- UniTask
- UniTask.DOTween
- DOTween.Modules
image.png

実装

using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class SimpleSceneTransition : MonoBehaviour
{
    [SerializeField] private float fadeTime = 1;
    [SerializeField] private Color transitionColor = Color.black;
    [SerializeField] private string nextScene;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) StartTransition();
    }

    async void StartTransition()
    {
        var g = new GameObject();
        DontDestroyOnLoad(g);
        var canvas = g.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        var image = g.AddComponent<Image>();
        transitionColor.a = 0;
        image.color = transitionColor;
        await image.DOFade(1, fadeTime);
        await SceneManager.LoadSceneAsync(nextScene);
        await image.DOFade(0, fadeTime);
        Destroy(g);
    }
}

UniTaskもDOTweenもわからんのじゃ!という人のための実装

ライブラリを使わずに一応書いてみましたがめちゃくちゃ長くなりました。
やはり定番どころのライブラリは勉強して使いこなせるようになったほうがよさそうですね。

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class SimpleSceneTransition : MonoBehaviour
{
    [SerializeField] private float fadeTime = 1;
    [SerializeField] private Color transitionColor = Color.black;
    [SerializeField] private string nextScene;

    private Image image;

    public static SimpleSceneTransition Instance { get; private set; }

    private void Start()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }

        var canvas = gameObject.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        image = gameObject.AddComponent<Image>();
        transitionColor.a = 0;
        image.color = transitionColor;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            StartCoroutine(StartTransition());
    }

    IEnumerator StartTransition()
    {
        float t = 0;
        while (true)
        {
            t = Mathf.Min(t + Time.deltaTime, fadeTime);
            transitionColor.a = t / fadeTime;
            image.color = transitionColor;
            if (t >= fadeTime) break;
            yield return null;
        }

        yield return SceneManager.LoadSceneAsync(nextScene);

        while (true)
        {
            t = Mathf.Max(t - Time.deltaTime, 0);
            transitionColor.a = t / fadeTime;
            image.color = transitionColor;
            if (t <= 0) break;
            yield return null;
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む