- 投稿日:2020-02-27T22:37:58+09:00
WPF フォルダ選択ダイアログ
■概要
■環境
- Windows 10
- Visual Studio 2019
- .NET Framework 4.8
■準備
NuGetで
Microsoft.WindowsAPICodePack-Shell
をインストール。■コード例
MainWindow.xaml<Window x:Class="FolderBrowse1.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:FolderBrowse1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Button Content="押せ" HorizontalAlignment="Left" Height="67" Margin="235,35,0,0" VerticalAlignment="Top" Width="109" Click="Button_Click"/> </Grid> </Window>MainWindow.xaml.csusing System.Windows; using MSAPI = Microsoft.WindowsAPICodePack; namespace FolderBrowse1 { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { /// <summary> /// コンストラクタ /// </summary> public MainWindow() { InitializeComponent(); } /// <summary> /// ボタンクリック時の処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Button_Click(object sender, RoutedEventArgs e) { var dlg = new MSAPI::Dialogs.CommonOpenFileDialog(); // フォルダ選択ダイアログ(falseにするとファイル選択ダイアログ) dlg.IsFolderPicker = true; // タイトル dlg.Title = "フォルダを選択してください"; // 初期ディレクトリ dlg.InitialDirectory = @"D:\Work"; if (dlg.ShowDialog() == MSAPI::Dialogs.CommonFileDialogResult.Ok) { MessageBox.Show($"{dlg.FileName}が選択されました。"); } } } }■実行
- 投稿日:2020-02-27T21:17:59+09:00
C#の単体テストは「Chaining Assertion」が便利
はじめに
C#の単体テストで期待値の確認は「Chaining Assertion」というライブラリがとても便利でお勧めです。
Chaining Assertion の公式サイトはこちら他のテスト記述ライブラリとの比較
値が等しいことの確認
テスト記述ライブラリで有名なものとして「Fluent Assertions」があります。
Fluent Assertions の公式サイトはこちら
それを用いてテストする場合、以下のように書きます。
(標準のAssertクラスよりも書きやすいので、こちらのライブラリもとても素晴らしいと思います)// Fluent Assertions で文字列が空文字であることを確認する例 theString.Should().Be(string.Empty);これに対して、Chaining Assertion は、Should().Be() でなく Is() だけで確認できるため、よりシンプルに記述できます。
// Chaining Assertion で文字列が空文字であることを確認する例 theString.Is(string.Empty);値が等しい以外(値が範囲内かなど)の確認
Fluent Assertions は期待値を確認するためのメソッド(BeNull、HaveLengthなど)をたくさん提供しています。これは一見すごく多機能で便利なのですが、実際にテストコードを書くときには適切なメソッドを調べることになるので意外と手間がかかります。
例えば、値が0以上10以下であることを確認しようと思った時に、範囲指定のメソッドがあるので、以下のように記述します。// Fluent Assertions で対象の値が0以上10以下であることを確認する例 theInt.Should().BeInRange(0, 10);Fluent Assertions の数値型の確認メソッドはこちら
それに対して Chaining Assertion は、シンプルに Is メソッドのみで期待値を確認します。Is の引数にラムダ式を書くことで、Is だけでどんな期待値も確認できます。以下のような感じです。
// Chaining Assertion で対象の値が0以上10以下であることを確認する例 theInt.Is(value => value >= 0 && value <= 10);C#開発者は、ラムダ式を書き慣れているので、確認したい内容に合わせてメソッドを探すよりも、全部ラムダ式で書く方が楽だと思います。
(これに関しては個人の感想ですが、私のチームで両方のライブラリをそれぞれ一定期間使ってみたところ、いずれのメンバーも Chaining Assertion の書き方の方が気に入りました)コレクションの確認
コレクションに関しては、Fluent Assertions はコレクション用のメソッドもいっぱいあります。適切なメソッドを探すのが少し手間です。Chaining Assertion 作者の河合さんが以下の記事で解説されているように、コレクションは慣れ親しんだ Linq to Objects で結果まで絞って True/Falseを判定する方が楽だと思います。
neue cc - メソッドチェーン形式のテスト記述ライブラリ// Fluent Assertions でコレクションの要素に3より大きい値があることを確認する例 // (何十個もあるコレクションのメソッドの中から適したメソッドを使う) collection.Should().HaveCountGreaterThan(3); // Chaining Assertion でコレクションの要素に3より大きい値があることを確認する例 // (慣れ親しんだ Linq to Objects を使う) collection.Any(c => c > 3).Is(true);その他の便利機能
Chaining Assertion の便利機能を紹介します。
プライベートメンバーへのアクセスが容易
期待値を確認するときに、privateメンバーにアクセスしたい場合があります。
その場合、PrivateObjectクラスを利用してアクセスします。ただ、PrivateObjectの実装は、PrivateObjectのインスタンスを作ってからそれを通してアクセスする必要があり、少し面倒です。
PrivateObjectクラスの実装例Chaining Assertion なら、privateメンバーへのアクセスを簡潔に実装できます。
具体的には、object型の拡張メソッドであるAsDynamic()メソッドを利用することでprivateメンバーにアクセスできます。
例を以下に示します。// privateメンバーを持つクラス public class MyClass { private string PrivateMethod(int number) { return number; } } [TestClass] public class UnitTest { [TestMethod] public void TestMethod() { var myClass = new MyClass(); // AsDynamic()の戻り値は dynamic型 // AsDynamic()に続けて呼び出したいメンバーを書くことでリフレクションで実行される int number = myClass.AsDynamic().PrivateMethod(3); number.Is(3); } }再帰的な公開フィールド・プロパティの確認
あるインスタンスを複製するメソッドを作った場合、その複製されたインスタンスのフィールド値および、そのインスタンスが保持しているオブジェクトが複製元と一致するか確認したい場合があります。
Chaining Assertion なら IsStructualEqualメソッドを呼び出すだけで、複製したオブジェクトのフィールド値およびそれが保持するオブジェクトについて、参照をたどって再帰的に確認できます(publicなフィールドまたはプロパティを確認します)。
以下に例を示します。// テスト対象クラス public class MyClass { public string Name { get; set; } public MyClass Child { get; set; } public MyClass Clone() { // 複製する処理 } } [TestClass] public class UnitTest { [TestMethod] public void TestMethod() { // テスト対象クラスを作成する var myClass = new MyClass() { Name = "Test", Child = new MyClass(), }; // 複製する var clone = myClass.Clone(); // 参照しているChildのフィールド値も含めて一致することを確認 clone.IsStructuralEqual(myClass); } }ただし、相互参照している場合は無限ループとなるため注意が必要です。
そのあたりの挙動を変更することも可能です。Chaining Assertion はインストールするとDLLを参照するのでなく、ソースコードを1ファイル追加するだけなので、そのソースコードを変更することで、自前で挙動をカスタマイズすることもできます。まとめ
Chaining Assertion は便利なのでお勧めです。
私は上記手法を用いて こちらのツール を作っています。
Twitterでも開発に役立つ情報を発信しています → @kojimadev
- 投稿日:2020-02-27T21:15:40+09:00
WinFormsのListView.SelectedIndicesが遅い問題を回避する
仮想モードにしたListViewでも、
SelectedIndices
プロパティ(ListView.SelectedIndexCollection
クラス)で非常に時間がかかるケースがあります。
(この記事では面倒なのでどれくらい時間がかかるかは述べません)遅くならないケース
SelectedIndices.Count
内部ではLVM_GETSELECTEDCOUNT
を使って実装されているので高速に取得できます。SelectedIndices[0]
1件目であれば、列挙を打ち切るため遅くはなりません。全選択/全選択解除を早くする
以下のように、ループで
SelectedIndices.Add
/Remove
でしていくと非常に時間がかかってしまいます。NGfor (var i = 0; i < listView1.Items.Count; i++) { listView1.SelectedIndices.Add(i); }ListBoxの全項目を高速に選択すると同様に、P/Invokeで全選択するようにします。
こんなのを定義して、
const int LVM_FIRST = 0x1000; const int LVM_SETITEMSTATE = LVM_FIRST + 43; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct LVITEM { public int mask; public int iItem; public int iSubItem; public int state; public int stateMask; [MarshalAs(UnmanagedType.LPTStr)] public string pszText; public int cchTextMax; public int iImage; public IntPtr lParam; public int iIndent; public int iGroupId; public int cColumns; public IntPtr puColumns; }; private const int LVIS_SELECTED = 2; public static void SelectAll(ListView control, bool select) { var lvItem = new LVITEM { stateMask = LVIS_SELECTED, state = select ? LVIS_SELECTED : 0 }; SendMessage(new HandleRef(control, control.Handle), LVM_SETITEMSTATE, new UIntPtr(unchecked((uint)-1)), ref lvItem); } [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern IntPtr SendMessage(HandleRef hWnd, int msg, UIntPtr wParam, ref LVITEM lvi);こう使います。
// 全選択 SelectAll(listview1, true); // 選択解除 SelectAll(listview1, false);選択行の取得を早くする
SelectedIndices
に対して列挙すると、内部では1件目から全部舐めてリストを作ることになるので、非常に遅くなります。
(foreach
で回さなくても、CopyTo
メソッドなども含む)
大きなインデックスでインデックスアクセスしても同様にその位置まで1件進めるのに時間がかかります。NGforeach (int i in listView1.SelectedIndices) { // iを使用した処理 }そのため、
VirtualItemsSelectionRangeChanged
とItemSelectionChanged
イベントで選択行を保持しておき、選択行が必要なときはそれを参照するようにします。private bool[] _selected = new bool[/*listView1.VirtualListSize分のサイズ*/]; private void listView1_VirtualItemsSelectionRangeChanged(object sender, ListViewVirtualItemsSelectionRangeChangedEventArgs e) { for (var i = e.StartIndex; i <= e.EndIndex; i++) { _selected[i] = e.IsSelected; } } private void listView1_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e) { _selected[e.ItemIndex] = e.IsSelected; }他の方法として、
- C#を諦めてC++/CLIで同等の処理を実装する
- 別途ネイティブDLLを作成してそれをP/Invokeで呼び出す
などもあります。
- 投稿日:2020-02-27T21:12:59+09:00
今更ながらWPFに置き換えてみる(4)
多分WPFの開発はXAMLでがっちり記述するのが大前提なんだろーな、と思ってはいるんですが、なんかスカスカ感があってついデザイナ中心で作ってしまいがちです。皆さんはどうなんでしょうか。
ということで、
・画面構成はほぼ今のFORMSのVBアプリと同様。
・時報機能あり。時間になるとこんな感じのNOTIFYが表示される・タスクバー内のNotificationも今と同様のバリエーション
・NotifyIconクリックでメイン画面をShowする際に、Screenのエッジにスナップ新たにこんなことができるかを確認しつつ可能であれば実装
・NotifyIconをデスクトップにドラッグすることでNotifyIconを消去しアプリのタスク専用の小Windowを表示。
・同様に逆に小WindowをタスクバーにドラッグすることでNotifyIcon化
(これイベントトラップ細工しないと難しそう)
・メインのウインドウのShow/Hideの際のトランジションをなんか工夫見た目重視。
プロセスのアイコン化ってそれほど特殊な考え方じゃないハズ。UNIXとかそうなんですよねよく知らないけど。
NeXTの適当にまねて作ったこんな感じのとか↓。
Macで最初に手に入れたThinkCって言語環境についてたリファレンスアプリがいい感じにやってたのを思い出して、あれを目標に。
- 投稿日:2020-02-27T18:00:03+09:00
【Unity】楽曲に合わせて〇〇するスクリプト
こんにちはっ?八ツ橋まろんです
個人バーチャルYouTuberとして、UnityのTimeline機能を使って、3Dモデルを使ったオリジナルのミュージックビデオを制作しています。
楽曲のリズムに合わせてパーティクルを出したり演出を入れたりしているのですが、Timelineの時間軸はフレーム単位(60フレーム/秒)なので、「100bpmの楽曲だから60/100 = 0.6秒 = 36フレームごとにパーティクルをEnableにして、、、」とか、「155bpmだと割り切れないから〇回に1回だけ1フレーム減らして、、、」なんて作業は、正直しんどいのでやっていられません。
なので、『最初の1回だけ指定したら残りは同じ時間サイクルで動作をしてくれるスクリプト』を書きました?(小難しくない簡単なスクリプトです)
BpmAction.csusing System.Collections; using UnityEngine; public class BpmAction : MonoBehaviour { public int bpm; enum Coef { quarter, half, x1, x2 } [SerializeField] Coef coef; [Header("Events")] public OnBPM onBPM; float BpmCoef() { switch (coef) { case Coef.quarter: return 0.25f; case Coef.half: return 0.5f; case Coef.x1: return 1f; case Coef.x2: return 2f; } return 1f; } [SerializeField] bool IsBpmAction; float time; private void Update() { if (IsBpmAction) { time += Time.deltaTime; if(time >= 60f / (bpm * BpmCoef())) { time -= 60f / (bpm * BpmCoef()); Action(); } } } void Action() { onBPM.Invoke(); } [System.Serializable] public class OnBPM : UnityEngine.Events.UnityEvent { } public void BpmActionStart() { IsBpmAction = true; Action(); } public void BpmActionStop() { IsBpmAction = false; time = 0; } public void SetNewBpm(int i) { BpmActionStop(); bpm = i; } }【ポイント】
以下の記述によって、uGUIのボタンのように、イベント登録ができるのでとっても便利です。覚えておきましょう。私も今回初めて知りました。(下の画像の赤枠部分が追加される)冒頭のハートの例では、ハートのAnimatorにSetTrigger("Beat")をして鼓動させています。
[Header("Events")] public OnBPM onBPM; [System.Serializable] public class OnBPM : UnityEngine.Events.UnityEvent { }また、時間の計測は、コルーチンを使わず、Update関数内でTime.deltatimeを毎フレーム足しています。
通常、〇〇時間後に起動する処理ではコルーチンをよく使いますが、更新はフレーム毎にしか行われないので、例えば「1.00秒の設定に対して1.01秒経った→1.00秒を越えているのでカウントを0にする」という処理では、毎回0.01秒遅いアクションになり、積もり積もって時間差が目立ってしまいます。そのため、「Updateで毎回Time.deltatimeを足す→1.01秒になったのでカウントを1.00秒引いて0.01秒から再スタート」とすることで正確な時間計測をしています。
(コルーチンでのやり方あったら教えてほしいです。よくわからんかった。)【使い方】
・BPMに楽曲のBPMを入れてください。・Unity2019以降:Timeline上にSignal Trackを追加し、最初の1回目の位置にSignalを置いてBpmActionStart()関数を呼んでやればOKです?終わるときはBpmActionStop()関数を呼んでください。
・Unity2018以前:TimelineにSignal機能がないため、追加でAnimatorを付けて、TimelineにAnimation Trackを追加して、IsBpmActionをtrueにすればOKです?終わるときはfalseにしてください。
・Coefは係数です。x1で毎ビート毎に、x2で2倍、halfで2回に1回、quarterで4回に1回です。
以上、「楽曲に合わせて〇〇するスクリプト」でしたっ?またね
八ツ橋まろん
- 投稿日:2020-02-27T15:00:05+09:00
WPFのコントロールにDirectX12で描画する
モチベーション
モーショングラフィックスエンジン的なものを作りたい。
グラフィックスの描画はDXRを見越してDirectX12が使いたい。
でも、周りのUIはグラフィックスとは分離して、MVC的なフレームワークで作りたい。Unityでいいじゃんって話なのですが…
やりたいこと
WPFで色々なパラメータやリソースを表示編集できるWindowの中に、DirextXが描画するコントロールを置きたい。
ポリ一枚表示しただけですし、スライダーで動くわけでもなければリサイズにも対応していませんが、
一応上記画像のようにできました。D3DImage/D3D11Image(今回は不採用)
WPFには
D3DImage
/D3D11Image
というクラスが用意されており、それぞれDirextX9/11向けのRenderTargetを提供しています。
WPFDXInteropDirectX12向けのクラスは提供されていませんが、DirectX11-12間でテクスチャリソースの共有が可能です。
DirectX11のWPFDXInteropでは、WPFControlからIDXGIResource
が手に入るので、それからID3D11Texture2D
を取得し、RenderTargetViewを作成し描画します。
DirectX12の場合も同様にIDXGIResource
からID3D12Resource1
を取得してRenderTargetViewを作成して描画すればいいはずです。
(DirectX12ではTextureやConstantBufferなどのインターフェイスとしての垣根がなくなり、ID3D12Resource1
に統一されました。)しかし、いざ実装してみようとすると、SwapChain使わない場合のやり方などがよくわからなかったので、確実な
HwndHost
を使ってみました。
ID3D12CommandQueue::ExecuteCommandLists
の後、IDXGISwapChain::Present
を呼ばずに、描画完了も待たずにリターンすればいいのでしょうか?WPFコントロールから渡されるIDXGIResource
はバックバッファなんですかね?とすると、描き切れなかったら半端なRTが表示されちゃう???HwndHost(WPF Control)とは
DirectX12のサンプルなどでは生Win32でGUIを生成して、ルートのWindowに対して描画をしています。
Win32のGUIというのは、以下のようなもので前半部がWindowの初期化、後半部がいわゆるUpdateやイベント(メッセージ)処理です。
HWNDという変数が出てきますが、これはFormやWPFでいうWindow
/Form
やControlに相当するもので文字通りのウインドウからボタンまでWindowとして扱い、HWNDで管理します。
DirectXにHWNDを指定して描画させる場合、このWindowの矩形領域内に描画することになります。main.cppint WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { WNDCLASS winc; // WNDCLASSの設定 HWND hwnd = CreateWindow( TEXT("MYCLASSNAME"), TEXT("Title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(&msg); theApp.Render(); } return msg.wParam; }それで、
HwndHost
はこのWINAPI
の枠を提供してくれます。
実質的にはWindowの矩形領域を提供してくれる&HWND
を保持管理してくれます。class MyControl : HwndHost { protected virtual HandleRef BuildWindowCore(HandleRef hwndParent) { // 初期化処理(ただし、この時点ではまだ矩形情報が取れない!) // CreateWindowとかして、帰ってきたHWNDを返す } protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { // Win32の後半のメッセージループの部分 // DirectXのUpdate(Render)処理はここで } protected virtual void DestroyWindowCore(HandleRef hwnd) { // 終了処理 } }ここにうまいことDirectX12の初期化処理とUpdate(Render)処理を挟みます。
この方法の最大の利点は、Win32で作る場合とほとんど変わらない…というかWin32との互換性維持のための機能という点です。
DirextXはそもそもゲームを作るためのAPIなので外部GUIは不要でWindowいっぱいに描けばいいので、手に入るサンプルやノウハウも大抵の場合は生Win32のルートWindowに描いています。それらを参考にできます。ということで、この
HwndHost
とWin32とDirectX12合体させてみたのがこちらになります。
DirectX12の処理は「DirectX12 Programming Vol.1」の付録のサンプルコードを流用させていただきました。DX.csclass DX : HwndHost { [Flags] enum WindowStyle : int { /* 省略 */ } IntPtr app = IntPtr.Zero; protected override HandleRef BuildWindowCore(HandleRef hwndParent) { // Win32のWindowの初期化 IntPtr hwnd = CreateWindowEx( 0, "STATIC", "", WindowStyle.WS_CHILD | WindowStyle.WS_VISIBLE, 0, 0, (int)ActualWidth, (int)ActualHeight, hwndParent.Handle, (IntPtr)WindowStyle.HOST_ID, IntPtr.Zero, 0); return new HandleRef(this, hwnd); } protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (app == IntPtr.Zero) { // DirectX12の初期化 // BuildWindowCoreでInitしたかったが、は矩形が0のままなのでDepthBufferが作れない。 // 本当はリサイズも考慮してデバイスのInitとRenderTarger/DepthBufferの生成を分けるべき。 app = Init(hwnd, (int)ActualWidth, (int)ActualHeight); } // DirectX12の描画(のリクエスト)処理 Render(app); handled = false; return IntPtr.Zero; } protected override void DestroyWindowCore(HandleRef hwnd) { // Win32のWindowとDirectX12の終了処理 DestroyWindow(hwnd.Handle); Dispose(app); } [DllImport("user32.dll")] static extern IntPtr CreateWindowEx( /* 省略 */ ); [DllImport("user32.dll")] static extern bool DestroyWindow(IntPtr hwnd); [DllImport("02_SimpleTriangle.dll")] static extern IntPtr Init(IntPtr hwnd, int width, int height); [DllImport("02_SimpleTriangle.dll")] static extern void Render(IntPtr app); [DllImport("02_SimpleTriangle.dll")] static extern IntPtr Dispose(IntPtr app); }Export.cppTriangleApp* Init(HWND hwnd, int width, int height) { auto app = new TriangleApp(); // TriangleApp.h app->Initialize(hwnd, width, height); return app; } void Render(TriangleApp* app) { app->Render(); } void Dispose(TriangleApp* app) { app->Cleanup(); delete app; }MainWindow.xaml<Window> <!-- 属性は省略 --> <Grid> <local:DX Margin="18,43,342,122"></local:DX> <WrapPanel Margin="527,101,53,281"> <TextBlock Text="Hogehoge"></TextBlock> </WrapPanel> <Slider Margin="517,149,39,231"></Slider> </Grid> </Window>DLLImportするときはImportするDLLが参照しているDLLもexeのディレクトリに並べないと
DllNotFoundException
が出ます。
この例では、02_SimpleTriangle.dll
がdxcompiler.dll
などを参照していてハマりました。全文は こちら(Github)
技術領域問題
WPFのコントロールは一枚のDirectX9で描かれています。そこに無理やりWin32のウインドウを乗っけてそこにDirextX12で描画している訳です。
なのでWPFコントロールでサンドイッチすることができません。詳しくは 技術領域の概要(MSDN)
余談
DirectX12の情報…少なすぎ!!
最近のMSの動向としてはDirectXを触るのは本当に限られた場合だけで、基本的にはUnityとか使ってね!君たちはDirectXなんて知らなくていいよ!ってスタンスですし、いまだにDirectX9が現役(?)ですからね。ゲームの専門学校などでもいまだにDirectX9と聞きます。確かにシンプルで初学向けなのかもしれませんが。
MSDNは情報「量」だけは結構ありますし、「DirectX12 Programming Vol.1」のおかげで基本的な使い方は理解できましたが、少し外れたことを、応用したいと思うと似たような例が見つかりませんね。参考
- 投稿日:2020-02-27T10:01:44+09:00
StreamingAssetsでTextureをロードする方法
はじめに
意外と、これだけのシンプルな内容の記事がなかったので端的に書いてみました。
対象
・unityのC#がわかる
・Resouces使いたくない
・コルーチンがわかる
・Actionがかわる
・WWW使いたくない本題
using UnityEngine.Networking; ------------------ private Texture textureTest; protected string path; public void Start() { //StreamingAssets内のTestフォルダのAというjpeg画像のパス指定 SetPath(Test/A.jpg); StartCoroutine (DownloadTexture (r => textureTest = r)); } //StreamingAssetsフォルダー内のパスの指定 public void SetPath (string url) { path = url; } public IEnumerator DownloadTexture (Action<Texture> callback) { //StreamingAssetsまでのパスと内側のパスをつなげる var url = System.IO.Path.Combine (Application.streamingAssetsPath, path); url = "file://" + url; using (UnityWebRequest www = UnityWebRequestTexture.GetTexture (url)) { yield return www.SendWebRequest (); if (www.isNetworkError || www.isHttpError) { Debug.Log (www.error); yield break; } var myTexture = ((DownloadHandlerTexture) www.downloadHandler).texture; //コルーチンは返り値を持てないので、Actionで返す callback (myTexture); }; }解説
SetPath(Test/A.jpg);この引数の部分を自分のフォルダーに変更してください。
最後に
StreamingAssetsはなんぞや
どういう利点が?
等は他に良記事が多数なのでググってみてください。
- 投稿日:2020-02-27T01:19:11+09:00
Google Calendar API #2
前回の記事
Google Calendar API #1環境
IDE:VisualStudio2019
アプリケーション:コンソールアプリ
フレームワーク:.NET Core 3.1カレンダーに予定を追加
前回は予定取得を試してみました。
今回はカレンダーの予定追加/更新を行ってみます。まずわかりやすいように自分のカレンダーの直近の予定を空にします。
予定を追加するコードを記述します。
以下公式にサンプルがありました。
Events: insert以下のようなスケジュール追加メソッドを用意
/// <summary> /// カレンダーイベントを追加 /// </summary> /// <param name="calendarId">カレンダーID</param> public void InsertEvent(string calendarId) { var newEvent = new Event() { Summary = "Google I/O 2020", Location = "神奈川県横浜市", Description = "テスト備考", Start = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 9:00:00"), TimeZone = "Asia/Tokyo", }, End = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 17:00:00"), TimeZone = "Asia/Tokyo", }, //Recurrence = new string[] { "RRULE:FREQ=DAILY;COUNT=2" }, //Attendees = new EventAttendee[] { // new EventAttendee() { Email = "lpage@example.com" }, // new EventAttendee() { Email = "sbrin@example.com" }, //}, //Reminders = new Event.RemindersData() //{ // UseDefault = false, // Overrides = new EventReminder[] { // new EventReminder() { Method = "email", Minutes = 24 * 60 }, // new EventReminder() { Method = "sms", Minutes = 10 }, // } //} }; var request = this.Serive.Events.Insert(newEvent, calendarId); var createdEvent = request.Execute(); Console.WriteLine("Event created: {0}", createdEvent.HtmlLink); }サンプル全文
規定クラスを作りました。
using Google.Apis.Auth.OAuth2; using Google.Apis.Services; using Newtonsoft.Json.Linq; using System.IO; namespace GoogleAPITest { /// <summary> /// GoogleAPI利用においての規定クラス /// </summary> public abstract class GoogleAPIBase<T> where T : IClientService { /// <summary> /// クライアントサービスインターフェース /// </summary> protected T Serive { get; set; } /// <summary> /// コンストラクタ /// </summary> /// <param name="keyJsonPath">APIキーのJSONファイルのパス</param> public GoogleAPIBase(string keyJsonPath, string[] scope) { var jObject = JObject.Parse(File.ReadAllText(keyJsonPath)); var serviceAccountEmail = jObject["client_email"].ToString(); var privateKey = jObject["private_key"].ToString(); var credential = new ServiceAccountCredential( new ServiceAccountCredential.Initializer(serviceAccountEmail) { Scopes = scope }.FromPrivateKey(privateKey)); this.Serive = this.CreateService(credential); } /// <summary> /// サービス作成メソッド /// </summary> /// <param name="credential">認証情報</param> /// <returns>クライアントサービスインターフェース</returns> protected abstract T CreateService(ICredential credential); } }カレンダーAPI以外でも使用できるように、サービス作成メソッドを継承先に強制し、サービスの型をジェネリックにしている。
カレンダーAPIテストクラス
using Google.Apis.Auth.OAuth2; using Google.Apis.Calendar.v3; using Google.Apis.Calendar.v3.Data; using Google.Apis.Services; using System; namespace GoogleAPITest.Calendar { /// <summary> /// カレンダーAPIテストクラス /// </summary> public class CalendarAPITest : GoogleAPIBase<CalendarService> { /// <summary> /// アプリケーション名 /// </summary> private const string APP_NAME = "Google Calendar API .NET"; /// <summary> /// カレンダーテストクラス /// </summary> public CalendarAPITest(string keyJsonPath) : base(keyJsonPath, new string[] { CalendarService.Scope.Calendar }) { } /// <summary> /// クライアントサービス作成 /// </summary> /// <param name="credential">認証情報</param> /// <returns>クライアントサービスインターフェース</returns> protected override CalendarService CreateService(ICredential credential) { return new CalendarService(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = APP_NAME }); } /// <summary> /// 予定読み取り /// </summary> /// <param name="calendarId">カレンダーID</param> public void ReadEvents(string calendarId) { // ここで第2引数にサービスアカウントに公開したカレンダーIDを指定する var request = new EventsResource.ListRequest(this.Serive, calendarId); request.TimeMin = DateTime.Now; request.ShowDeleted = false; request.SingleEvents = true; request.MaxResults = 10; request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime; // List events. var events = request.Execute(); Console.WriteLine("Upcoming events:"); if (events.Items != null && events.Items.Count > 0) { foreach (var eventItem in events.Items) { var when = eventItem.Start.DateTime.ToString(); if (String.IsNullOrEmpty(when)) { when = eventItem.Start.Date; } Console.WriteLine("{0} start:({1}) end:({2})", eventItem.Summary, when, eventItem.End.DateTime.ToString()); } } else { Console.WriteLine("No upcoming events found."); } } /// <summary> /// カレンダーイベントを追加 /// </summary> /// <param name="calendarId">カレンダーID</param> public void InsertEvent(string calendarId) { var newEvent = new Event() { Summary = "Google I/O 2020", Location = "神奈川県横浜市", Description = "テスト備考", Start = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 9:00:00"), TimeZone = "Asia/Tokyo", }, End = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 17:00:00"), TimeZone = "Asia/Tokyo", }, //Recurrence = new string[] { "RRULE:FREQ=DAILY;COUNT=2" }, //Attendees = new EventAttendee[] { // new EventAttendee() { Email = "lpage@example.com" }, // new EventAttendee() { Email = "sbrin@example.com" }, //}, //Reminders = new Event.RemindersData() //{ // UseDefault = false, // Overrides = new EventReminder[] { // new EventReminder() { Method = "email", Minutes = 24 * 60 }, // new EventReminder() { Method = "sms", Minutes = 10 }, // } //} }; var request = this.Serive.Events.Insert(newEvent, calendarId); var createdEvent = request.Execute(); Console.WriteLine("Event created: {0}", createdEvent.HtmlLink); } } }プログラムメインエントリ
using Google.Apis.Auth.OAuth2; using Google.Apis.Calendar.v3; using Google.Apis.Services; using Google.Apis.Util.Store; using GoogleAPITest.Calendar; using Newtonsoft.Json.Linq; using System; using System.IO; using System.Threading; namespace GoogleAPITest { /// <summary> /// メインクラス /// </summary> public class Program { /// <summary> /// メインエントリ /// </summary> /// <param name="args">実行時引数</param> public static void Main(string[] args) { try { // カレンダーID var calendarId = "カレンダーID"; // Googleカレンダーテストクラスインスタンス化 var calApi = new CalendarAPITest( @"C:\job\TestProject\GoogleAPITest\testproject-269217-813bf9be17a5.json"); // イベント読み取り calApi.ReadEvents(calendarId); // イベント追加 calApi.InsertEvent(calendarId); } catch (Exception err) { Console.WriteLine(err.Message); } finally { Console.Read(); } } } }次回は更新をやってみたいと思います。
- 投稿日:2020-02-27T01:19:11+09:00
Google Calendar APIを使用してみる #2
シリーズ
Google Calendar APIを使用してみる #1
Google Calendar APIを使用してみる #3環境
IDE:VisualStudio2019
アプリケーション:コンソールアプリ
フレームワーク:.NET Core 3.1カレンダーに予定を追加
前回は予定取得を試してみました。
今回はカレンダーの予定追加/更新を行ってみます。まずわかりやすいように自分のカレンダーの直近の予定を空にします。
予定を追加するコードを記述します。
以下公式にサンプルがありました。
Events: insert以下のようなスケジュール追加メソッドを用意
/// <summary> /// カレンダーイベントを追加 /// </summary> /// <param name="calendarId">カレンダーID</param> public void InsertEvent(string calendarId) { var newEvent = new Event() { Summary = "Google I/O 2020", Location = "神奈川県横浜市", Description = "テスト備考", Start = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 9:00:00"), TimeZone = "Asia/Tokyo", }, End = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 17:00:00"), TimeZone = "Asia/Tokyo", }, //以下があるとエラーになっていたのでコメントアウト・・・ //Recurrence = new string[] { "RRULE:FREQ=DAILY;COUNT=2" }, //Attendees = new EventAttendee[] { // new EventAttendee() { Email = "lpage@example.com" }, // new EventAttendee() { Email = "sbrin@example.com" }, //}, //Reminders = new Event.RemindersData() //{ // UseDefault = false, // Overrides = new EventReminder[] { // new EventReminder() { Method = "email", Minutes = 24 * 60 }, // new EventReminder() { Method = "sms", Minutes = 10 }, // } //} }; var request = this.Serive.Events.Insert(newEvent, calendarId); var createdEvent = request.Execute(); Console.WriteLine("Event created: {0}", createdEvent.HtmlLink); }サンプル全文
基底クラスを作りました。
using Google.Apis.Auth.OAuth2; using Google.Apis.Services; using Newtonsoft.Json.Linq; using System.IO; namespace GoogleAPITest { /// <summary> /// GoogleAPI利用においての基底クラス /// </summary> public abstract class GoogleAPIBase<T> where T : IClientService { /// <summary> /// クライアントサービスインターフェース /// </summary> protected T Serive { get; set; } /// <summary> /// コンストラクタ /// </summary> /// <param name="keyJsonPath">APIキーのJSONファイルのパス</param> public GoogleAPIBase(string keyJsonPath, string[] scope) { var jObject = JObject.Parse(File.ReadAllText(keyJsonPath)); var serviceAccountEmail = jObject["client_email"].ToString(); var privateKey = jObject["private_key"].ToString(); var credential = new ServiceAccountCredential( new ServiceAccountCredential.Initializer(serviceAccountEmail) { Scopes = scope }.FromPrivateKey(privateKey)); this.Serive = this.CreateService(credential); } /// <summary> /// サービス作成メソッド /// </summary> /// <param name="credential">認証情報</param> /// <returns>クライアントサービスインターフェース</returns> protected abstract T CreateService(ICredential credential); } }カレンダーAPI以外でも使用できるように、サービス作成メソッドを継承先に強制し、サービスの型をジェネリックにしている。
カレンダーAPIテストクラス
using Google.Apis.Auth.OAuth2; using Google.Apis.Calendar.v3; using Google.Apis.Calendar.v3.Data; using Google.Apis.Services; using System; namespace GoogleAPITest.Calendar { /// <summary> /// カレンダーAPIテストクラス /// </summary> public class CalendarAPITest : GoogleAPIBase<CalendarService> { /// <summary> /// アプリケーション名 /// </summary> private const string APP_NAME = "Google Calendar API .NET"; /// <summary> /// カレンダーテストクラス /// </summary> public CalendarAPITest(string keyJsonPath) : base(keyJsonPath, new string[] { CalendarService.Scope.Calendar }) { } /// <summary> /// クライアントサービス作成 /// </summary> /// <param name="credential">認証情報</param> /// <returns>クライアントサービスインターフェース</returns> protected override CalendarService CreateService(ICredential credential) { return new CalendarService(new BaseClientService.Initializer() { HttpClientInitializer = credential, ApplicationName = APP_NAME }); } /// <summary> /// 予定読み取り /// </summary> /// <param name="calendarId">カレンダーID</param> public void ReadEvents(string calendarId) { // ここで第2引数にサービスアカウントに公開したカレンダーIDを指定する var request = new EventsResource.ListRequest(this.Serive, calendarId); request.TimeMin = DateTime.Now; request.ShowDeleted = false; request.SingleEvents = true; request.MaxResults = 10; request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime; // List events. var events = request.Execute(); Console.WriteLine("Upcoming events:"); if (events.Items != null && events.Items.Count > 0) { foreach (var eventItem in events.Items) { var when = eventItem.Start.DateTime.ToString(); if (String.IsNullOrEmpty(when)) { when = eventItem.Start.Date; } Console.WriteLine("{0} start:({1}) end:({2})", eventItem.Summary, when, eventItem.End.DateTime.ToString()); } } else { Console.WriteLine("No upcoming events found."); } } /// <summary> /// カレンダーイベントを追加 /// </summary> /// <param name="calendarId">カレンダーID</param> public void InsertEvent(string calendarId) { var newEvent = new Event() { Summary = "Google I/O 2020", Location = "神奈川県横浜市", Description = "テスト備考", Start = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 9:00:00"), TimeZone = "Asia/Tokyo", }, End = new EventDateTime() { DateTime = DateTime.Parse("2020/02/28 17:00:00"), TimeZone = "Asia/Tokyo", }, //Recurrence = new string[] { "RRULE:FREQ=DAILY;COUNT=2" }, //Attendees = new EventAttendee[] { // new EventAttendee() { Email = "lpage@example.com" }, // new EventAttendee() { Email = "sbrin@example.com" }, //}, //Reminders = new Event.RemindersData() //{ // UseDefault = false, // Overrides = new EventReminder[] { // new EventReminder() { Method = "email", Minutes = 24 * 60 }, // new EventReminder() { Method = "sms", Minutes = 10 }, // } //} }; var request = this.Serive.Events.Insert(newEvent, calendarId); var createdEvent = request.Execute(); Console.WriteLine("Event created: {0}", createdEvent.HtmlLink); } } }プログラムメインエントリ
using Google.Apis.Auth.OAuth2; using Google.Apis.Calendar.v3; using Google.Apis.Services; using Google.Apis.Util.Store; using GoogleAPITest.Calendar; using Newtonsoft.Json.Linq; using System; using System.IO; using System.Threading; namespace GoogleAPITest { /// <summary> /// メインクラス /// </summary> public class Program { /// <summary> /// メインエントリ /// </summary> /// <param name="args">実行時引数</param> public static void Main(string[] args) { try { // カレンダーID var calendarId = "カレンダーID"; // Googleカレンダーテストクラスインスタンス化 var calApi = new CalendarAPITest( @"C:\job\TestProject\GoogleAPITest\testproject-269217-813bf9be17a5.json"); // イベント読み取り calApi.ReadEvents(calendarId); // イベント追加 calApi.InsertEvent(calendarId); } catch (Exception err) { Console.WriteLine(err.Message); } finally { Console.Read(); } } } }次回は更新をやってみたいと思います。
次回