20190625のC#に関する記事は13件です。

【C#】Outlook予定表取得

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

using Microsoft.Office.Interop.Outlook;

namespace WindowsFormsApp1
{

    public partial class Form1 : Form
    {
        static bool flag = true;

        /*** Form1.cs ***/
        public Form1()
        {
            InitializeComponent();
            new Thread(new ThreadStart(GetMouseButton)).Start();
            this.ShowInTaskbar = false;
        }

        public void GetMouseButton()
        {
            while (flag is true)
            {
                if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left)
                {
                    getText();
                    //Console.WriteLine("マウスのXBUTTON2が押されています。");
                }
                //Thread.Sleep(100);
            }
        }

        public delegate void Delegate();

        public void getText()
        {
            try
            {
                if (InvokeRequired)
                {
                    Invoke(new Delegate(getText));
                    return;
                }

                Point cp = this.PointToClient(new Point(Cursor.Position.X, Cursor.Position.Y));
                //Console.WriteLine(cp.X.ToString() + " " + cp.Y.ToString());
                if ((cp.X < 0 || this.Width < cp.X ) && (cp.Y < 0 || this.Height < cp.Y) )
                {
                    this.Location = new Point(Cursor.Position.X + 10, Cursor.Position.Y + 15);
                }

                Microsoft.Office.Interop.Outlook.Application outlook = new Microsoft.Office.Interop.Outlook.Application();
                NameSpace ns = outlook.GetNamespace("MAPI");
                MAPIFolder oFolder = ns.GetDefaultFolder(OlDefaultFolders.olFolderCalendar);

                textBox1.Text = "";
                if (outlook.ActiveExplorer().Selection.Count is 1)
                {
                    Object selObject = outlook.ActiveExplorer().Selection[1];
                    if (selObject is AppointmentItem)
                    {
                        AppointmentItem oAppoint = (selObject as AppointmentItem);
                        textBox1.Text += "開催:" + oAppoint.Organizer + "\r\n";
                        textBox1.Text += "件名:" + oAppoint.Subject + "\r\n";
                        textBox1.Text += "開始:" + oAppoint.Start.ToString("yyyy/MM/dd HH:mm:ss") + "\r\n";
                        textBox1.Text += "終了:" + oAppoint.End.ToString("yyyy/MM/dd HH:mm:ss") + "\r\n";
                        textBox1.Text += "場所:" + oAppoint.Location + "\r\n";
                        Clipboard.SetText("開催:" + oAppoint.Organizer + "\n" 
                                        + "件名:" + oAppoint.Subject + "\n" 
                                        + "開始:" + oAppoint.Start.ToString("yyyy/MM/dd HH:mm:ss") + "\n"
                                        + "終了:" + oAppoint.End.ToString("yyyy/MM/dd HH:mm:ss") + "\n"
                                        + "場所:" + oAppoint.Location + "\n");
                    }
                    else
                    {
                    }
                }
            }
            catch (System.Exception ex)
            {
            }
        }

        private void notifyIcon1_MouseDoubleClick_1(object sender, MouseEventArgs e)
        {
            // フォームを表示する
            this.Visible = true;
            // 現在の状態が最小化の状態であれば通常の状態に戻す
            if (this.WindowState == FormWindowState.Minimized)
            {
                this.WindowState = FormWindowState.Normal;
            }
            // フォームをアクティブにする
            new Thread(new ThreadStart(GetMouseButton)).Start();
            this.Activate();
        }

        private void Form1_SizeChanged(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                flag = false;
            }
            else
            {
                flag = true;
                new Thread(new ThreadStart(GetMouseButton)).Start();
            }
        }

        private void Form1_FormClosing_1(object sender, FormClosingEventArgs e)
        {
            flag = false;
        }
    }
}

参考

MAPI over HTTP を使用すると、Outlook 2010、2013、2016 で Exchange に正しく接続できない
 ↑関係あるかわからないけど
[C#] Outlookの予定表から予定を取得する
方法: プログラムによって現在の Outlook アイテムを確認します。
【C#入門】Timerで処理を一定間隔で繰り返す方法
現在どのマウスボタンが押されているか調べる
マウスの位置を取得し続ける(C#)
チェックボックスがチェックされているか取得する (C#プログラミング)
C#を使って最小化した時にタスクトレイに格納する
C#: タスクトレイに常駐するアプリの作り方
タスクバーにフォームを表示しないようにする
画面座標をクライアント座標(コントロール上の座標)に変換する

http://hogeo.blog.jp/archives/1072823745.html

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

C# ソースコードのフローチャートを生成する(2)

はじめに

前回ソースコードのフローチャートを作成するところまで書きました。
今回トレースするところまで実装します。
頻繁に使うことは無いと思われるので全自動化するのは最初から断念しています。
以下のような手順でトレースを行います。
手順.png

トレース用のソースコードを作成する

変換前ソース

以下のソースコードで試してみます。

Source.cs
public class Source
{
    public bool Working { get; set; } = true;
    public int Value { get; set; } = 0;
    public void DoJob()
    {
        var t = Task.Run(async () =>
        {
            //稼動中繰り返す
            while(Working)
            {
                //値を判断
                switch (Value)
                {
                    case 0:
                        //処理開始
                        System.Console.WriteLine("処理開始");
                        break;
                    case 1:
                        //更新開始
                        System.Console.WriteLine("更新開始");
                        break;
                    case 6:
                        System.Console.WriteLine("ここにはこない");
                        break;
                    default:
                        //値が上限まで達したか
                        if(Value >= 5)
                        {
                            //更新終了
                            System.Console.WriteLine("更新終了");
                            //処理終了とする
                            Working = false;
                        }
                        break;
                }
                await NewValue();
                await Task.Delay(500);
            }                
        });
        t.Wait();
    }

    private async Task NewValue()
    {
        Command.ReqValue(Value);
        await Task.Delay(1000);
        Value++;            
    }
}

変換後(トレース用)ソース

前回も書きましたが、これをトレースするにはトレース用のコードを埋め込みます。

  • FlowTrace はトレース用のクラスです。
  • FlowTrace#InitGraphは初期状態のフローを作成します。
  • FlowTrace#TraceNodeは通過したノードを記憶します。
  • 処理開始時("node1"の個所)TraceNodeの第2引数をtrueにしていますが、これにより関数間のエッジを作成しません。
    デフォルト(false)だと関数間のエッジを作成しますが、共通関数などあっちこっちからエッジが繋がってしまい見にくくなります。
  • whileなどのループする構文("node3"の個所)はループに入らないこともあるので、ループに入る前と入った直後の2か所にトレースを埋め込みます。
  • Mainは手書きで追加しています。
  • 今回の実装ではendターミナル("node18")のトレースを埋め込んでいません。
    必要であれば手で埋め込みます。
ConvSource.cs
public class ConvSource
{
    public bool Working { get; set; } = true;
    public int Value { get; set; } = 0;

    //手書き--ここから
    //args[0]:dotファイル(トレース結果)出力先
    static void Main(string[] args)
    {
        var conv = new ConvSource();
        conv.InitGraph();
        conv.DoJob();
        conv.Trace.WriteDot(args[0]);
    }
    //手書き--ここまで

    public void DoJob()
    {
        Trace.TraceNode("node1", true);
        Trace.TraceNode("node2");
        var t = Task.Run(async () =>
        {
            //稼動中繰り返す
            Trace.TraceNode("node3");
            while (Working)
            {
                //値を判断
                Trace.TraceNode("node3");
                Trace.TraceNode("node4");
                switch (Value)
                //省略
    }

    public FlowTrace Trace = new FlowTrace();
    public void InitGraph()
    {
        Trace.AddFunc(0);
        Trace.AddNode(0, "node1", "ellipse", "DoJob\nstart");
        //省略
        Trace.AddEdge("node1", "node2", "");
        Trace.AddEdge("node4", "node5", "case 0:");
        //省略    
  }

出力結果

赤が通過個所、括弧内は回数を示します。
初期フローは非同期処理に対応していないので、トレースすることにより想定外のエッジ(「Unintended」と表記)が追加されることがあります。
このサンプルの場合、Task.Run()のラムダ式が実行される前にt.Wait()が実行されているみたいです。このような動きになるとは知りませんでした。
syntax3.txt.png

変換プログラムのソースコード

Dotファイル出力およびソースコード変換

前回掲載したコード(FlowGraph.cs)にMain処理を統合し、トレース用ソースコードの変換処理を追加しています。
変更が無い部分(CreateGraphNode以降)は省略しています。
またSyntaxVisitorも同様に省略しています。(一部修正しています)

FlowGraph.cs
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using System.IO;

namespace Flow
{
    public class FlowGraph
    {
        private static int WORD_WRAP_LENGTH = 40;
        private List<GraphEdge> EdgeList = new List<GraphEdge>();
        private Dictionary<SyntaxNode, GraphNode> NodeMap = new System.Collections.Generic.Dictionary<SyntaxNode, GraphNode>();
        private IEnumerable<BaseMethodDeclarationSyntax> memberDeclarations;
        private string _sourceCode;
        private int _seq = 0;

        //args[0]:対象c#ソースファイル
        //args[1]:dotファイル(静的フローチャート)出力先
        //args[2]:変換後ソース(トレース用コード)出力先
        static void Main(string[] args)
        {
            string sourceCode = File.ReadAllText(args[0]);
            FlowGraph graph = new FlowGraph(sourceCode);
            //静的フロー出力
            graph.WriteDot(args[1]);
            //ソースコード変換
            graph.ConvSource(args[2]);
        }

        public FlowGraph(string sourceCode)
        {
            _sourceCode = sourceCode;
            var tree = CSharpSyntaxTree.ParseText(_sourceCode);
            memberDeclarations = tree.GetRoot().DescendantNodes().OfType<BaseMethodDeclarationSyntax>();
            if(memberDeclarations.Count() == 0)
            {
                return;
            }
            foreach (var funcRoot in memberDeclarations)
            {
                SyntaxVisitor ast = new SyntaxVisitor(this);
                if (funcRoot is MethodDeclarationSyntax || funcRoot is ConstructorDeclarationSyntax)
                    ast.VisitRootSyntax(funcRoot);
            }
        }

        //トレース用のソースコード出力
        public void ConvSource(string outFile)
        {
            var result = _sourceCode;
            var keys = new List<SyntaxNode>(NodeMap.Keys).OrderByDescending(x => x.Span.Start);
            AddInitStatements(ref result, memberDeclarations.Last());
            foreach (var sNode in keys)
            {
                AddTraceStatement(ref result, NodeMap[sNode], sNode);
            }
            using (System.IO.StreamWriter writer = new System.IO.StreamWriter(outFile, false))
            {
                writer.Write(result);
            }
        }

        //ノード、エッジ登録関数部作成
        private void AddInitStatements(ref string result, SyntaxNode lastMetod)
        {
            var clustermap = new Dictionary<SyntaxNode, int>();
            int ipoint = lastMetod.FullSpan.End;
            string spaces = new string(' ', lastMetod.GetLocation().GetLineSpan().StartLinePosition.Character);
            var sb = new StringBuilder();
            sb.Append("\r\n" + spaces + "public FlowTrace Trace = new FlowTrace();\r\n");
            sb.Append(spaces + "public void InitGraph()\r\n");
            sb.Append(spaces + "{\r\n");
            //クラスタIDを追加する
            string spaces2 = new string(' ', spaces.Length + 4);
            int cluster = 0;
            foreach (var funcRoot in memberDeclarations)
            {
                sb.Append(spaces2 + "Trace.AddFunc(" + cluster + ");\r\n");
                foreach (SyntaxNode sNode in NodeMap.Keys)
                {
                    if (sNode == funcRoot || sNode.Ancestors(true).Contains(funcRoot))
                    {
                        var gNode = NodeMap[sNode];
                        string isFunc = sNode == funcRoot ? "true" : "false";
                        string label = gNode.LabelName.Replace("\r", "").Replace("\n", "\\n").Replace("\"", "\\\"");
                        sb.Append(spaces2 + "Trace.AddNode(" + cluster + ", \"" + gNode.NodeId + "\", \"" + gNode.NodeShape + "\", \"" + label + "\"" + ");\r\n");
                    }
                }
                cluster++;
            }

            foreach (GraphEdge gEdge in EdgeList)
            {
                string label = gEdge.CenterLabel.Replace("\r", "").Replace("\n", "\\n").Replace("\"", "\\\"");
                sb.Append(spaces2 + "Trace.AddEdge(\"" + gEdge.TailNode.NodeId + "\", \"" + gEdge.HeadNode.NodeId + "\", \"" + label + "\");\r\n");
            }

            sb.Append(spaces + "}\r\n");
            result = result.Insert(ipoint, sb.ToString());
        }

        //トレースコード埋め込み
        private void AddTraceStatement(ref string result, GraphNode gNode, SyntaxNode sNode)
        {
            string traceStr = $"Trace.TraceNode(\"{gNode.NodeId}\");";
            var parent = sNode.Parent;
            if (sNode is BlockSyntax)
            {
                return;
            }
            else if (sNode is MethodDeclarationSyntax ||
                sNode is ConstructorDeclarationSyntax ||
                sNode is LocalFunctionStatementSyntax)
            {
                var property = sNode.GetType().GetProperty("Body");
                var block = (BlockSyntax)property.GetValue(sNode);
                string spaces = new string(' ', block.GetLocation().GetLineSpan().StartLinePosition.Character + 4);
                result = result.Insert(block.Span.Start + 1, "\r\n" + spaces + $"Trace.TraceNode(\"{gNode.NodeId}\", true);");
                return;
            }
            else if (parent is ElseClauseSyntax || parent is IfStatementSyntax)
            {
                var property = sNode.Parent.GetType().GetProperty("Statement");
                var statement = property.GetValue(sNode.Parent);
                string spaces = new string(' ', parent.GetLocation().GetLineSpan().StartLinePosition.Character);
                result = result.Insert(sNode.Span.End, "\r\n" + spaces + "}");
                string spaces2 = new string(' ', sNode.GetLocation().GetLineSpan().StartLinePosition.Character);
                result = result.Insert(((StatementSyntax)statement).Span.Start, "{" + "\r\n" + spaces2 + traceStr + "\r\n" + spaces2);
                return;
            }
            else if (sNode is ExpressionSyntax expr)
            {
                if (expr.Parent is DoStatementSyntax doSntax2)
                {
                    try
                    {
                        var lastStatement = doSntax2.Statement.ChildNodes().Last();
                        string spaces = new string(' ', lastStatement.GetLocation().GetLineSpan().StartLinePosition.Character);
                        result = result.Insert(lastStatement.Span.End, "\r\n" + spaces + traceStr);
                    }
                    catch { }
                }
                return;
            }
            else if (sNode is DoStatementSyntax ||
                sNode is WhileStatementSyntax ||
                sNode is ForEachStatementSyntax ||
                sNode is ForStatementSyntax)
            {
                var property = sNode.GetType().GetProperty("Statement");
                StatementSyntax statement = (StatementSyntax)property.GetValue(sNode);
                try
                {
                    var firstStatement = statement.ChildNodes().First();
                    string spaces = new string(' ', firstStatement.GetLocation().GetLineSpan().StartLinePosition.Character);
                    result = result.Insert(firstStatement.Span.Start, traceStr + "\r\n" + spaces);
                }
                catch { }
            }
            string leftPadding = new string(' ', sNode.GetLocation().GetLineSpan().StartLinePosition.Character);
            result = result.Insert(sNode.Span.Start, traceStr + "\r\n" + leftPadding);
        }

        public GraphNode CreateGraphNode(SyntaxNode astNode, string labelName, string nodeShape)
        //以下省略
    }
}

トレース用クラス

Dotファイル出力部などFlowGraphとの重複が多いですが前回のコードの見直しは最小限としました。

FlowTrace.cs
using System.Collections.Generic;
using System.Linq;

namespace Flow
{
    public class FlowTrace
    {
        private Dictionary<string, Node> nodeMap = new Dictionary<string, Node>();
        private Dictionary<(string tailId,string headId),Edge> edgeMap = new Dictionary<(string tailId, string headId), Edge>();
        private Dictionary<int, string> funcMap = new Dictionary<int, string>();

        public void AddNode(int funcId, string id, string shape, string label)
        {
            nodeMap[id] = new Node {FuncId = funcId, Id = id, Shape = shape, Label = label };
        }

        public void AddEdge(string tailId,string headId,string label)
        {
            edgeMap[(tailId, headId)] = new Edge {TailId = tailId,HeadId = headId , Label = label };
        }

        public void AddFunc(int id)
        {
            funcMap[id] = string.Empty;
        }
        public void TraceNode(string nodeId, bool reset = false)
        {
            if (nodeMap.ContainsKey(nodeId))
            {
                var node = nodeMap[nodeId];
                if(funcMap[node.FuncId] != nodeId)
                {
                    nodeMap[nodeId].PassCount++;
                    if (reset)
                    {
                        funcMap[node.FuncId] = string.Empty;
                    }
                    if (funcMap[node.FuncId] != string.Empty)
                    {
                        var edgeKey = (funcMap[node.FuncId], nodeId);
                        if (!edgeMap.ContainsKey(edgeKey))
                        {
                            edgeMap[edgeKey] = new Edge { TailId = funcMap[node.FuncId], HeadId = nodeId, Label = "Unintended", LineStyle = "dashed" };
                        }
                        edgeMap[edgeKey].PassCount++;
                    }
                    funcMap[node.FuncId] = nodeId;
                }                
            }
        }

        public void WriteDot(string dotFilename)
        {
            using (System.IO.StreamWriter writer = new System.IO.StreamWriter(dotFilename, false))
            {
                writer.WriteLine("digraph G{");
                writer.WriteLine("rankdir=TB;");
                writer.WriteLine("node[fontname = \"MS GOTHIC\"]");
                writer.WriteLine("edge[fontname = \"MS GOTHIC\"]");
                var clusters = new List<int> { -1 };
                clusters.InsertRange(0, funcMap.Keys);
                foreach (var cluster in clusters)
                {
                    if(cluster != -1)
                    {
                        writer.WriteLine("subgraph cluster_" + cluster + "{");
                    }                    
                    foreach (Node node in nodeMap.Values.OrderBy((x) => x.Id))
                    {                       
                        if (node.FuncId == cluster)
                        {
                            string label = node.Label.Replace("\n", "\\n").Replace("\"", "\\\"");
                            string countStr = string.Empty;
                            string colorStr = string.Empty;
                            if (node.PassCount > 0)
                            {
                                countStr = "(" + node.PassCount + ")";
                                colorStr = ", color=\"red\"";
                            }
                            writer.WriteLine("\"" + node.Id + "\"" + " [shape = \"" + node.Shape + "\""
                                      + colorStr + ", label = \"" + label + countStr + "\"]");
                        }
                    }
                    if (cluster != -1)
                    {
                        writer.WriteLine("}");
                    }
                }


                foreach (Edge edge in edgeMap.Values.OrderBy((x) => x.TailId))
                {
                    string countStr = string.Empty;
                    string colorStr = string.Empty;
                    if (edge.PassCount > 0)
                    {
                        countStr = "(" + edge.PassCount + ")";
                        colorStr = ", color=\"red\"";
                    }
                        writer.WriteLine("\"" + edge.TailId + "\"  -> \"" + edge.HeadId + "\""
                         + " [label =\"" + edge.Label + countStr + "\"" + colorStr + "]");
                }
                writer.WriteLine("}");
            }
        }

        private class Node
        {
            public int FuncId { get; set; }
            public string Id {get;set;}
            public string Shape { get; set; }
            public string Label { get; set; }
            public long PassCount { get; set; } = 0;
        }

        private class Edge
        {
            public string TailId { get; set; }
            public string HeadId { get; set; }
            public string Label { get; set; }
            public long PassCount { get; set; }
            public string LineStyle { get; set; } = string.Empty;
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Entity Framework Coreで文字列カラムとEnumを双方向にマッピングする

この記事で使用しているコードは以下にあります。
https://github.com/noobow34/MappingStringToEnumInEntityFrameworkCore
何も考えずそのまま実行すれば動きます。
SQLiteを使用しているのでデータベースのファイルは存在しなくても勝手に作成されます。
実際に作成されたSQLiteのデータベースで結果を確認してみてください。

また、本記事ではC#と表記しますが、基本的にはVB.NETでも同様です。

やりたいこと

タイトルのとおりです。
想定としては、よくある"〇〇コード"のような決まった値しか取らないDB上の文字列カラム(charやvarchar)に対して
エンティティクラス側(C#側)でもEnumを使って取りうる値を限定してやるということです。

今回例として、USERテーブルのGENDERカラム(性別)は男性'M'、女性'F'のみが設定され得るとします。

C#のEnumは内部的にはintなので、Javaと違って値が文字列(string)となるEnumは言語の標準機能では使用することはできません。
また、エンティティクラス側でそのプロパティの型をEnumにする場合、文字列との変換をどのように行うのかを定義する必要があります。
以上より、今回の目的を達成するためには大きく2つのステップが必要です。

1.文字列のEnumを定義する
Enumのメンバが文字列(string)の値を持つ必要があります。
また、string⇔Enumの双方向の変換方法が必要になります。
Enum→string:データベースの文字列カラムに保存するので必要になる
string→Enum:データベースから取得してきた文字列をEnumとしてエンティティクラスのプロパティに格納するときに必要になる
2.文字列カラムをエンティティクラス側上でEnumで扱う(Enumにマッピングする)

1.文字列のEnumを定義する

C#の言語標準機能では文字列のEnumを定義することはできませんが、
アトリビュートと拡張メソッドを使って実現する方法があります。
例えば以下の記事が例です。
C# > enumに文字列を割り当てる。
ですが、自分でアトリビュートや拡張メソッドを定義しなくても、ライブラリがあります。
今回これを使います。nugetでプロジェクトに取り込んでください。
EnumStringValues

以下のようにStringValueアトリビュートでEnumに対応する文字列を定義します。

GenderEnum.cs
//~~~略 全体はGitHubからGenderEnum.csをみてください
public enum GenderEnum
{
    [StringValue("M")]
    Male,
    [StringValue("F")]
    Female
}
//~~~略

そうすると、Enumの.GetStringValue()で文字列が取得できるようになります。

GenderEnum.Male.GetStringValue(); //"M"が返ってくる
GenderEnum.Female.GetStringValue(); //"F"が返ってくる

逆に文字列(今回でいうと"F","M")をEnumにするには.ParseToEnum<T>()を使用します。

"M".ParseToEnum<GenderEnum>(); //GenderEnum.Maleが返ってくる
"F".ParseToEnum<GenderEnum>(); //GenderEnum.FeMaleが返ってくる
//
string gender = "M";
gender.ParseToEnum<GenderEnum>(); //当然、定数ではなくて変数でもOK

これで、string⇔Enumの双方向の変換が実現できました。

2.文字列カラムをエンティティクラス上でEnumで扱う(Enumにマッピングする)

まず、エンティティクラス上では当然ですが該当のプロパティの型をEnumにします。

User.cs
//~~~略 全体はGitHubからUser.csをみてください
[Column("GENDER")]
public GenderEnum? Gender { get; set; } //プロパティの型をEnumにしておく
//~~~略

では
・データベースから取得してきた値をどのようにEnumに変換してプロパティに格納するのか
・Enumからどのように文字列に変換してデータベースに登録するのか
をどうやって定義するのでしょうか?
値の変換(Value Conversion) を使用します。
DbContextのOnModelCreatingで以下のように定義します。

TestDbContext.cs
//~~~略 全体はGitHubからTestDbContext.csをみてください
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //Conversionを使用してEnumとの対応関係を定義
    modelBuilder.Entity<User>() //Userエンティティの
       .Property(u => u.Gender) //Genderプロパティに
       .HasConversion //値の変換を設定
       //EnumをGetStringValueしたものがDBに登録される
       (g => g.GetStringValue()
       //DBから取得した値をParseToEnumしたものがEnumとしてプロパティに格納される
       , g => ((string)g).ParseToEnum<GenderEnum>());
}
//~~~略

これで、めでたくEnumのプロパティを使用してデータベースと双方向でやり取り(登録、取得)ができます。

実行してみる

Program.cs
//~~~略 全体はGitHubからProgram.csをみてください
//登録
using (var context = new TestDbContext(options))
{
    context.Users.Add(new User { Name = "Bob", Gender = GenderEnum.Male }); //Maleは'M'として登録される
    context.Users.Add(new User { Name = "Elizabeth", Gender = GenderEnum.Female }); //Femaleは'F'として登録される
    context.SaveChanges();
}

//取得
using (var context = new TestDbContext(options))
{
    foreach (var user in context.Users)
    {
        Console.WriteLine($"Id:{user.Id}");
        Console.WriteLine($"Name:{user.Name}");
        Console.WriteLine($"Gender(Enum):{user.Gender}");
        Console.WriteLine($"Gender(String):{user.Gender.GetStringValue()}");
        Console.WriteLine("---------------------------");
    }
}

実行結果
Enumとstringの変換が正しく行われているのがわかります。

Id:1
Name:Bob
Gender(Enum):Male
Gender(String):M
---------------------------
Id:2
Name:Elizabeth
Gender(Enum):Female
Gender(String):F
---------------------------

データベース上では'M','F'で登録されているのがわかります。
image.png

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

xamarin TabbedPageでタブ移動時にListViewの更新

TabbedPageにて、複数のListViewを用意し、ステータス(既読/読書中)によってリストを切り替えるインターフェースを作成した時に、タブを選択したタイミングでリストの表示内容を更新したい時がある。
そんな時は、該当タブのContentPageのAppearingイベントを利用することで実現可能だ。

sample.xaml
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:controls="clr-namespace:sample;assembly=books2" 
            x:Class="sample">

    <TabbedPage.Children>
        <ContentPage Title="既読" Padding="30" Appearing="Handle_Appearing0">
        <ListView x:Name="_listView0">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="1" />    
                            <RowDefinition Height="20" />
                            <RowDefinition Height="40" />
                            <RowDefinition Height="1" /> 
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1" />
                            <ColumnDefinition Width="50" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="1" />
                        </Grid.ColumnDefinitions>
                        <Image Source="{Binding ImageL}"
                               Grid.Row="1" Grid.Column="1" Grid.RowSpan="2"
                               VerticalOptions="Start"
                               Aspect="AspectFit"
                               />
                        <Label Grid.Row="1" Grid.Column="2"
                               Text="{Binding Title}" 
                               FontAttributes="Bold"                                
                               />
                        <Label Grid.Row="2" Grid.Column="2"
                               Text="{Binding Author}"
                               FontSize="12"
                               />
                    </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        </ContentPage>    
        <ContentPage Title="読書中" Padding="30" Appearing="Handle_Appearing1">
        <ListView x:Name="_listView1">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="1" />    
                            <RowDefinition Height="20" />
                            <RowDefinition Height="40" />
                            <RowDefinition Height="1" /> 
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1" />
                            <ColumnDefinition Width="50" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="1" />
                        </Grid.ColumnDefinitions>
                        <Image Source="{Binding ImageL}"
                               Grid.Row="1" Grid.Column="1" Grid.RowSpan="2"
                               VerticalOptions="Start"
                               Aspect="AspectFit"
                               />
                        <Label Grid.Row="1" Grid.Column="2"
                               Text="{Binding Title}" 
                               FontAttributes="Bold"                                
                               />
                        <Label Grid.Row="2" Grid.Column="2"
                               Text="{Binding Author}"
                               FontSize="12"
                               />
                    </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        </ContentPage>    
    </TabbedPage.Children>
</TabbedPage>

sample.xaml.cs
        void Handle_Appearing0(object sender, System.EventArgs e)
        {
            // リストビューのデータを更新
            _listView0.ItemsSource =
                wSqliteControl.GetItems(0);
        }

引っ張り更新は実現できていたものの、Androidだとリストにデータが表示されていないと引っ張れないやんけ!!
となって悶々としてましたが、ようやく解決できました。

同じような悩みを抱えている方の助けに少しでもなれば幸いです。

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

C#でPrint関数とRead関数を実装してみた

C#でPrint関数を実装してみた

はい、初めに言うけどあまり使い所の多くないネタ投稿的内容だ。
C#とかJavaとかって、出力文一つ書くのが長いんだよ!
Console.Writelineだとか、System.out.printlnだったりと。
Pythonならprint一つで済むのに!!
と思ったので実装。

using System;

class test
{

    static void Print(string a)
    {
        Console.WriteLine(a);
    }

    static void Print(string a, string b)
    {
        Console.WriteLine("{0}と{1}",a,b);
    }


    public static void Main()
    {
       Print("a"); 
    }

}

はい、これだけ。
一応引数2つまで受け取れるようにしてみた
もっと実用性のあるようにしたかったけどアイディア不足()

Read関数も実装してみた。

上で言った通り長すぎるから打つのだるいねん!!ってことで実装。

using System;

class test
{
    static void Read()
    {
        var hogehoge = Console.ReadLine();
        return hogehoge;
    }

    public static void Main()
    {
       var hoge = Read()
    }
}

こっちはまぁ、Printと比べたらまだ使い勝手が良さそう
Read()だけで入力読み込みできるのは悪くないね!!

はい、まとめたコードと使用例?
的なもの

using System;

class test
{

    static void Print(string a)
    {
        Console.WriteLine(a);
    }

    static void Print(string a, string b)
    {
        Console.WriteLine("{0}と{1}",a,b);
    }

    static string Read()
    {
        var hogehoge = Console.ReadLine();
        return hogehoge;
    }
    public static void Main()
    {
        var a = Read();
        Print(a);
    }
}

はい、これで3とか入れるとそのまま3って表示されるよ。
うん、クソ簡単なコードだね(笑)
Readは便利かもしれないのでよかったら使ってみてな。。。
では、お疲れ!

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

ジャンプしている最中にzキーを押して空中攻撃をする方法

閲覧用1.png
閲覧用2.png
閲覧用3.png
なかなかネットの記事でいい方法が見つからなくてだいぶん苦戦しています。
方法やアドバイスを教えていただけると嬉しいです。
ちなみにジャンプまでの作ったアニメ―ションやコードは画像の通りです。

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

ジャンプしている最中にzキーを押して空中攻撃をする方法知りたいです。[Unity]

閲覧用1.png
閲覧用2.png
閲覧用3.png
なかなかネットの記事でいい方法が見つからなくてだいぶん苦戦しています。
方法やアドバイスを教えていただけると嬉しいです。
ちなみにジャンプまでの作ったアニメ―ションやコードは画像の通りです。

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

Unity2018.3以降のPrefab作成

2018.3以降では、NestedPrefab対応でPrefabUtilityが変わっていて、
色々調べたのでPrefabUtilityの使い方のメモとして残しておこうと思います。

#スクリプトからPrefab作成

EditorスクリプトでFBXデータからそのままPrefabを作成する。
2018.2以前はこれでプレファブ作成できた。

old.cs
  var obj = AssetDatabase.LoadAssetAtPath("Asset/fbx.fbx", typeof(GameObject)) as GameObject;
 PrefabUtility.CreatePrefab("Asset/prefab.prefab",obj);
 AssetDatabase.SaveAssets();

ところが、2018.3以降は「PrefabUtility.CreatePrefab()」が非推奨になっていた。
いつなくなるかわからないので、この際新しい書き方に切り替えてみようと思う。

色々試した結果、以下のコードでいけるようだ

new.cs
{
  var obj = AssetDatabase.LoadAssetAtPath("Asset/fbx.fbx", typeof(GameObject)) as GameObject;
  // インスタンス化
  var game = PrefabUtility.InstantiatePrefab(obj) as GameObject;

 // 〜 処理 〜

  // プレファブ作成
  var prefab = PrefabUtility.SaveAsPrefabAsset(game, "Asset/prefab.prefab");
 // リンクを解除
  PrefabUtility.UnpackPrefabInstance(game, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
 // シーンから削除  
 Object.DestroyImmediate(game);
 AssetDatabase.SaveAssets();
}

以前はシーンにインスタンスを作成しなくてもできたが、今回からは一度シーンに作成しないとダメのようなので
最後にシーンから削除するようにした。

ちなみに、リンクについてはあんまりわかっていない。。即反映されるわけでもなさそうだし。。
リンクについては今後調べていこうと思います。

なお、リンクなしでインスタンス化するメソッドもある模様?(インスタンス化時にCloneとつく)

 var game = PrefabUtility.InstantiateAttachedAsset(obj) as GameObject;

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

xBehave.net + Fluent Assertionsを使ったら思いの外便利だった

2つを組み合わせて使ったら便利だったのでメモ。
ちなみに2つとも日本語記事は全然見かけなかったので、なんで日本では流行らないのかなーとちょっと悲しかった(´・ω・`)
流行ることを願ってタグ振っておきます。
※わかる方とか想像つく方いましたらコメントください

経緯

  • もともとNUnit使っていたところに興味からXUnitを使ってみた
  • アサーションエラーのメッセージは Assert.True() とか一部のメソッドにしか実装されてない
  • NUnitではコメント書く派だったので何かやり方ないかなと『xunit message』あたりで検索してみる
  • 某英語のQAサイト で紹介あったものを使ってみたら便利だった

問題点

経緯にも記載してるけど、アサーションエラーのメッセージがほとんど書けない 実装になっているらしい。

Equalメソッドはメッセージが書けない
[Fact]
public void T01Equalでアサーション() {
    var expValue = 2;
    var actual1 = Add1(1); // 1を足すメソッドを想定
    // Assert.Equal(expValue, actual1, "actual1 がおかしい"); // <= これが記述できない
    Assert.Equal(expValue, actual1);
    var actual2 = Add1(2);
    Assert.Equal(expValue, actual2); // ここで失敗する
}
テスト結果1、ただ失敗したとしか出ない
  X  XUnitSample.SampleTests.T01Equalでアサーション [6ms]
  エラー メッセージ:
   Assert.Equal() Failure
Expected: 2
Actual:   3

Assert.True()Assert.False() はメッセージが書けるけど、エラー時の具体的な値が見えなくなってしまう。

テスト実装2、Trueはメッセージが書けるけど失敗した値がわからない
[Fact]
public void T02Trueでアサーション() {
    var expValue = 2;
    var actual1 = Add1(1);
    Assert.True(expValue == actual1, "actual1がおかしい");
    var actual2 = Add1(2);
    Assert.True(expValue == actual2, "actual2がおかしい"); // ここで失敗する
}
テスト結果2、どんな値の整合で間違ったのか見えない
  X XUnitSample.SampleTests.T02Trueでアサーション [5ms]
  エラー メッセージ:
   actual2がおかしい
Expected: True
Actual:   False

ライブラリ

ライブラリ全体の機能は多いので、実際に試した範囲のみを取り上げます。
(要約:ちゃんと使いこなしてはいません)

xBehave.net

ライセンス:MIT License
自然言語でシナリオの説明を記載することができるライブラリ。

Use natural language to describe your scenarios.

具体的には string に拡張メソッド生やして、元の string にエラーメッセージ(というかケース)を記載していくイメージ。
string.x(Action) 内のアクションがそれぞれの説明内容のステップとして実行される。
ケースをまたがる変数についてはローカル変数またはシナリオメソッドの引数として宣言する。
TheoryData の代替となる Example を使いたい場合は引数で宣言すること。

テスト実装3、xBehaveのScenarioでの実装
[Scenario] // <= Factの代わりにxBehaveの属性を使用
public void T03Scenarioでアサーション() {
    int expValue = 0;
    int actual;
    "値初期化"
        .x(() => { expValue = 2; });
    "引数1の結果を確認"
        .x(() => { actual = Add1(1); Assert.Equal(expValue, actual); });
    "引数2の結果を確認"
        .x(() => { actual = Add1(2); Assert.Equal(expValue, actual); }); // ここで失敗する
    "引数3の結果を確認"
        .x(() => { actual = Add1(3); Assert.Equal(expValue, actual); });
}

// public void T03~~(int expValue, int actual) としてローカル変数なしでもよい

テストがステップ毎に検証され、エラーが起きると付加情報としてステップ名が一緒に表示される。

テスト結果3
  X XUnitSample.SampleTests.T03Scenarioでアサーション() [03] 引数2の結果を確認 [1ms]
  エラー メッセージ:
   Assert.Equal() Failure
Expected: 2
Actual:   3
  ! XUnitSample.SampleTests.T03Scenarioでアサーション() [04] 引数3の結果を確認 [1ms]

ちなみに Visual Studio のテストエクスプローラーからテストを実行すると、

  • どこまでが成功してどこで失敗したか、どのステップが処理されてないか
  • 各ステップ内での処理時間

についてテスト毎に メソッド内部のステップ単位で表示される のでさらに便利。

テストエクスプローラーでScenarioメソッドの結果を表示

注意事項

Documentation > Writing scenarios のNoteに記載されているが、テスト処理は値の代入だろうがAssertだろうが
すべて string.x() の内部で記述する必要がある。

Note: You should not be writing any code inside a scenario method which is outside one of the step definitions.
It doesn't make sense to do so, since a scenario method only exists in order to define the steps, and it is executed
in a context which makes that assumption.

テスト実装4、意図した処理にならない例
[Scenario]
public void T04Scenarioの誤った書き方() {
    int expValue = 2;
    int actual = Add1(1); // ここでは2になる
    "引数1の結果を確認"
        .x(() => { Assert.Equal(expValue, actual); });
    actual = Add1(2); // ここでは3になる
    "引数2の結果を確認"
        .x(() => { Assert.Equal(expValue, actual); }); // ここで失敗するはず
}

テストケースのメソッドが全て処理されてから各ステップが処理されるらしい。

テスト結果4、『引数1の結果を確認』の段階でactualが3になっている
  X XUnitSample.SampleTests.T04Scenarioの誤った書き方() [01] 引数1の結果を確認 [2ms]
  エラー メッセージ:
   Assert.Equal() Failure
Expected: 2
Actual:   3
  ! XUnitSample.SampleTests.T04Scenarioの誤った書き方() [02] 引数2の結果を確認 [1ms]

ダイナミックなテストケース(非公式)

a scenario method only exists in order to define the steps を逆手に取った使い方。
公式の Documentation にはそれっぽいことが記載されていなかったけどやってみたらできたのでメモ。

[Scenario]
public void T21ダイナミックなテストステップ() {
    Enumerable.Range(0, 10)
        .ToList()
        .ForEach(i => {
            $"{i} が5以下であるか確認"
            .x(() => i.Should().BeLessOrEqualTo(5));
        });
}

ステップが動的に作成されてそれぞれテストされている。

  X XUnitSample.SampleTests.T21ダイナミックなテストステップ() [07] 6 が5以下であるか確認 [1ms]
  エラー メッセージ:
   Expected i to be less or equal to 5, but found 6.
  ! XUnitSample.SampleTests.T21ダイナミックなテストステップ() [08] 7 が5以下であるか確認 [1ms]
  ! XUnitSample.SampleTests.T21ダイナミックなテストステップ() [09] 8 が5以下であるか確認 [1ms]
  ! XUnitSample.SampleTests.T21ダイナミックなテストステップ() [10] 9 が5以下であるか確認 [1ms]

具体的な使い方としては、以下のようなテスト処理を用意しておくとインファイルとアウトファイル用意するだけでいろんなパターンの検証追加したりできそう。(未検証)

var inDir = new DirectoryInfo("input");
var outDir = "output";
input.EnumerateFiles("*.txt").ToList().ForEach(file => {
    var outFile = Path.Combine(outDir, file.Name);
    // TargetFuncがテスト対象の処理
    $"テストデータ {file.Name} の確認"
        .x(()=> TartetFunc(File.ReadAllText(file.FullName))
                    .Should().Be(File.ReadAllText(outFile)));
});

Fluent Assersions

ライセンス:Apache License 2.0
テストの期待値を自然な形で指定できるようにするメソッド拡張のライブラリ。

A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests.

具体的には各データ型に Should() 拡張メソッドを生やしてアサーションラッパーを生成、ラッパーに対して各アサーションメソッドを呼び出すことで検証していく。

テスト実装5、基本的な使い方
[Fact]
public void T05Fluentアサーション() {
    var expValue = 2;
    var actual1 = Add1(1);
    actual1.Should().Be(expValue, "期待値と違う");
    var actual2 = Add1(2);
    actual2.Should().Be(expValue, "期待値と違う"); // ここで失敗する
}

エラーメッセージも独自に整形されるのだが、嬉しいのが Should() の呼び出し元 がメッセージに表示されること。
どうやってるんだろうこれ(*´ω`)

テスト結果5、呼び出し元の名称を含めてエラーメッセージが生成される
  X XUnitSample.SampleTests.T05Fluentアサーション [95ms]
  エラー メッセージ:
   Expected actual2 to be 2 because 期待値と違う, but found 3.

なお、変数でなく数式やメソッドチェーンでも行ける模様。

(actual2 + 3).Should().Be(expValue + 3, "期待値と違う");

// => Expected (actual2 + 3) to be 5 because 期待値と違う, but found 6.

一つのアサーションラッパーに対して複数の検証を行うことも可能。

テスト実装6、
[Fact]
public void T06Fluentのチェーン() {
    var actual = new[] { 1, 3, 5, 7, 9 };
    actual.Should().NotBeEmpty()
        .And.HaveCount(5)
        .And.ContainInOrder(1, 7, 5);
}
テスト結果6
  X XUnitSample.SampleTests.T06Fluentのチェーン [115ms]
  エラー メッセージ:
   Expected actual {1, 3, 5, 7, 9} to contain items {1, 7, 5} in order, but 5 (index 2) did not appear (in the right order).

また Should() は元の型に対して異なるラッパーを返すので、型に応じてある程度自然に記述できるようになっている。
ただしこの辺は特性慣れないとハマることもあるかもしれないので注意。

テスト実装7、型毎のShouldの違い
[Fact]
public void T07型毎のShouldの違い() {
    var actual1 = new[] { 1, 3, 5, 7, 9 };
    actual1.Should().Equal(1, 3, 5, 7, 9); // コレクションに対しては要素それぞれの一致を検証
    var actual2 = Add1(2);
    actual2.Should().Equals(3); // 数値に対しては単体の等価を検証
    actual2.Should().BeGreaterThan(2); // 数値のラッパーで利用可能なメソッド
    // actual1.Should().BeGreaterThan(2); // <= コレクションに対しては実装されていない
}

組み合わせて実装

テスト実装8、組み合わせ
[Scenario]
public void T11xBehaveFluent組み合わせ() {
    int expValue = 0;
    "値初期化"
        .x(() => { expValue = 2; });
    "引数の結果を確認"
        .x(() => {
            Add1(1).Should().Be(expValue);
            Add1(2).Should().Be(expValue); // ここで失敗する。
            Add1(3).Should().Be(expValue);
        });
    "ここは処理されない"
        .x(() => { });
}

どのステップで失敗して具体的にどの検証に対してNGなのかがわかりやすくなって嬉しい(๑•̀ㅂ•́)و✧

テスト結果8
  X XUnitSample.SampleTests.T11xBehaveとFluent組み合わせ() [02] 引数の結果を確認 [2ms]
  エラー メッセージ:
   Expected Add1(2) to be 2, but found 3.
  ! XUnitSample.SampleTests.T11xBehaveとFluent組み合わせ() [03] ここは処理されない [1ms]

参考まとめ

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

C# で SalesForce の Bulk API を叩く

やりたいこと

SalesForce の bulk API で csv ファイルによって大量の情報を更新したい。

公式ドキュメント

Bulk API 2.0

大まかな流れ

  • SFログインでセッションID取得
  • ジョブ作成
  • ジョブにデータ追加
  • ジョブのステータスを更新(ジョブ開始)
  • ジョブの状態監視
  • ジョブの結果取得

実装

SFログインでセッションID取得

OAuthでの認証ができるみたいだけど今回は
SOAPのログイン処理でセッションID loginResult.sessionId を取得

ジョブ作成

/// <summary>
/// job作成
/// </summary>
private bool CreateJob()
{
    // エンドポイント
    string url = DOMAIN + "/services/data/v46.0/jobs/ingest";

    // メソッド
    string method = "POST";

    // content-type
    string contentType = "application/json";

    // リクエストヘッダ
    string header = "Authorization: Bearer " + loginResult.sessionId;

    // リクエストボディ
    // JSON形式 でリクエスト作成(シリアライズ処理等は割愛)
    JobCreateRequest jcr = new JobCreateRequest();
    jcr.operation = "upsert";   // 操作 : insert delete update upsert 
    jcr.obj = "Contact";        // 操作対象オブジェクト名
    jcr.contentType = "CSV";    // コンテンツタイプ CSVのみ
    jcr.lineEnding = "CRLF";    // CSVの改行タイプ :  LF CRLF
    jcr.externalIdFieldName = "Id"; // 外部 ID 項目 更新/挿入操作で必須
    string body = CreateJson(jcr);

    // HTTPリクエスト(具体的な実装は割愛)
    var res = HTTPAccess.GetResponse(method, url, body, contentType, header);

    if (res.StatusCode != 200) return false;

    // パースしてジョブIDを取得(具体的な実装は割愛)
    JobInfo jobInfo = ParseJson(res.body);
    this.jobId = jobInfo.id;

    return true;
}

ジョブにデータ追加

base64 エンコードで 150MB 以内の制限があるため 100MB 以下に抑える

/// <summary>
/// データ追加
/// </summary>
private bool AddDataToJob()
{
    // エンドポイント
    string url = DOMAIN + "/services/data/v46.0/jobs/ingest/" + this.jobId + "/batches";

    // メソッド
    string method = "PUT";

    // content-type
    string contentType = "text/csv";

    // リクエストヘッダ
    string header = "Authorization: Bearer " + loginResult.sessionId;

    // リクエストボディ
    // csv形式で入力  ジョブ作成時に指定した外部 ID 項目は必須
    string body = "Id,LastName,FirstName,AccountId" + "\r\n";
    body += "003000000000000,バルク,太郎,001000000000000" + "\r\n";
    body += "003000000000001,バルク,次郎,001000000000000";

    // HTTPリクエスト(具体的な実装は割愛)
    var res = HTTPAccess.GetResponse(method, url, body, contentType, header);

    // 成功時は201
    return (res.StatusCode == 201);
}

ジョブのステータスを更新(ジョブ開始)

/// <summary>
/// ジョブステータス更新
/// </summary>
/// <param name="state">UploadComplete : データアップロード完了,ジョブをキューに追加 / Aborted : ジョブ中止</param>
/// <returns></returns>
private bool UpdateJobState(string state)
{
    // エンドポイント
    string url = DOMAIN + "/services/data/v46.0/jobs/ingest/" + this.jobId;

    // メソッド
    string method = "PATCH";

    // content-type
    string contentType = "application/json";

    // リクエストヘッダ
    string header = "Authorization: Bearer " + loginResult.sessionId;

    // リクエストボディ
    // state を更新する
    string body = "{\"state\":\"" + state + "\"}";

    // HTTPリクエスト(具体的な実装は割愛)
    var res = HTTPAccess.GetResponse(method, url, body, contentType, header);

    return (res.StatusCode == 200);
}

ジョブの状態監視

いつ処理されるかサーバ次第なので
適当なタイミングで完了チェックが必要になる

/// <summary>
/// ジョブ監視
/// </summary>
private void CheckJob()
{
    while (true) {
        // エンドポイント
        string url = DOMAIN + "/services/data/v46.0/jobs/ingest/" + this.jobId;

        // メソッド
        string method = "GET";

        // リクエストヘッダ
        string header = "Authorization: Bearer " + loginResult.sessionId;

        // HTTPリクエスト(具体的な実装は割愛)
        var res = HTTPAccess.GetResponse(method, url, null, null, header);

        // パースしてジョブIDを取得(具体的な実装は割愛)
        JobInfo jobInfo = ParseJson(res.body);
        if (jobInfo.state == "JobComplete" || jobInfo.state == "Failed")
        {
            // 完了または失敗になるまで監視する
            break;
        }
        else
        {
            Console.WriteLine("まだだよ");
            Thread.Sleep(3000);
        }
    }
}

ジョブの結果取得

/// <summary>
/// ジョブ結果取得
/// </summary>
private bool GetJobResult()
{
    // エンドポイント
    // 成功結果取得
    string url = DOMAIN + "/services/data/v46.0/jobs/ingest/" + this.jobId + "/successfulResults";
    // 失敗結果取得
    //string url = DOMAIN + "/services/data/v46.0/jobs/ingest/" + this.jobId + "/failedResults";

    // メソッド
    string method = "GET";

    // リクエストヘッダ
    string header = "Authorization: Bearer " + loginResult.sessionId;

    // HTTPリクエスト(具体的な実装は割愛)
    var res = HTTPAccess.GetResponse(method, url, null, null, header);

    MessageBox.Show(res.Body);

    return (res.StatusCode == 200);
}

おまけ

API のバージョン取得
ログインしなくても使用可能
エンドポイントのvXX.Xを切り替えて使用する

private void GetAPIVersion()
{
    // エンドポイント
    string url = DOMAIN + "/services/data";

    // メソッド
    string method = "GET";

    // HTTPリクエスト(具体的な実装は割愛)
    var res = HTTPAccess.GetResponse(method, url, null, null, null);

    List<APIVersion> apis = new List<APIVersion>();
    apis = ParseJson(res.body);

    // 必要あればソート
    //apis.Sort((a, b) => string.Compare(a.version, b.version));

    // 最新のバージョンを設定
    this.apiVersion = apis[apis.Count - 1].version;
}

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

ローカルファイルパスを URI オブジェクトに変換する

かなりどうでもいい小ネタです。たまにしかやらないとこういうもののほうがむしろ忘れるのでメモを兼ねて。

設定ファイルをURLで読み込むところがありまして。

ただ、デバッグ時にはローカルのファイルをデフォルト値にしたかったんですね。Uriといえばファイルスキームも使えます file:// でも、プロジェクトに置いたファイルをこれでどう指し示すかわかりません。相対URIも形式としてはあるけど、違いそう。

もののドキュメントには Uri クラスにはローカルファイルパスも渡せそうなので、

var uri = new Uri("hogehoge.txt");

みたいに書いてみたんですが、これもうまくいきません。

なので、もうFileInfoを経由することにしました。

var file = new FileInfo("hogehoge.txt");
var uri = new Uri(file.FullName);

これで大丈夫でした。

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

Unityエディタ拡張でサウンド再生を行う

経緯

Unityのエディタ拡張でサウンドコンテナなどを作ってた時に保持したAudioClipの内容を再生したい( 該当AudioClipの場所にInspectorを移動したくない )場合に調べたのでここに共有

ちなみにデフォでサウンドファイル選択時に音声は鳴らせる→ここを参照

スクリプト

AudioClipの再生はここを参考に作成
停止方法が見つからなかったので探してきた

using System;
using UnityEngine;


#if UNITY_EDITOR
using System.Reflection;
using UnityEditor;
#endif


namespace Sample
{
    // SE,BGMの管理用コンテナクラス.
    [System.Serializable]
    public class SampleContainer : ScriptableObject
    {

        // 保持するデータ.
        [SerializeField] AudioClip soundData;


#if UNITY_EDITOR
        // SampleContainerのインスペクタ拡張.
        [CustomEditor(typeof(SampleContainer))]
        class SampleContainerEditor : UnityEditor.Editor
        {

            // インスペクタ描画.
            public override void OnInspectorGUI()
            {
                var data = target as SampleContainer;

                data.soundData = (AudioClip)EditorGUILayout.ObjectField("AudioClip", data.soundData, typeof(AudioClip));

                if (GUILayout.Button("PlayClip"))
                    this.PlayClip(data.soundData);

                if (GUILayout.Button("StopClip"))
                    this.StopClip(data.soundData);
            }


            // エディタ上でのサウンド再生.
            void PlayClip( 
                AudioClip clip)
            {
                if (clip == null) return;

                var unityEditorAssembly = typeof(AudioImporter).Assembly;
                var audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");
                var method = audioUtilClass.GetMethod
                (
                    "PlayClip",
                    BindingFlags.Static | BindingFlags.Public,
                    null,
                    new Type[] {typeof(AudioClip)},
                    null
                );

                method.Invoke(null, new object[] {clip});
            }


            // エディタ上でのサウンドを停止する.
            void StopClip(
                AudioClip clip)
            {
                if (clip == null) return;

                var unityEditorAssembly = typeof(AudioImporter).Assembly;
                var audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");
                var method = audioUtilClass.GetMethod(
                    "StopClip",
                    BindingFlags.Static | BindingFlags.Public,
                    null,
                    new Type[] {typeof(AudioClip)},
                    null
                );

                method.Invoke(null, new object[] {clip});
            }

        }
#endif
    }
}

実行画面

こんな感じ
image.png

ScriptableObjectをインスタンス化してAudioClipを設定して "Play" ボタンを押下すると再生 "Stop" を押下すると停止

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

[C#/WPF]ビューモデルからビューのメソッドを呼ぶ代わりに、EventTriggerでMouseDown等のイベントを拾ってビューのメソッドを呼ぶ

やりたいこと

以前の記事で、View(コードビハインド)でしかできないことがあるので、コードビハインドに書いたメソッドを、ViewModelから呼ぶ、ということを実験した。

ViewModelのタイミングで(例えば、ボタンを押したときにプロパティの値をif分で判定して、条件を満たすときだけ)Viewのメソッドを呼ぶ、とかだと以前の記事のやり方をしないといけなさそうだが、だた単純に「ボタンを押されたときにViewのメソッドを呼びたい」という場合なら、もっと簡易的に簡単にできる方法を思いついたので備忘メモする。

やり方

下記を使う。

  • Interaction.Triggers
  • EventTrigger
  • CallMethodAction

やることとしては、

  • Interaction.TriggersとEventTriggerで何をトリガーにするか決めて
  • CallMethodActionでコードビハインドに書いたメソッドを呼ぶ

だけ。

サンプルコード

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        Name="Root">
    <Grid>
        <TextBlock Margin="40" Background="Yellow">この黄色いTextBlockを押すと、トリガーが発火します
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseDown">
                    <!-- ViewModelのメソッドの呼び方1 -->
                    <i:InvokeCommandAction Command="{Binding func}"/>
                    <!-- ViewModelのメソッドの呼び方2(ViewModelの中のメソッドを呼ぶ) -->
                    <ei:CallMethodAction TargetObject="{Binding}" MethodName="EventFunc"/>
                    <!-- ViewModelのメソッドの呼び方3(コードビハインドの中のメソッドを呼ぶ) -->
                    <ei:CallMethodAction TargetObject="{Binding ElementName=Root}" MethodName="CodeBehindFunc"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBlock>
    </Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ViewModel();
        }

        public void CodeBehindFunc()
        {
            MessageBox.Show("CallMethodAction でトリガー発火(コードビハインドのメソッド)");
        }
    }
}
ViewModel.cs
using System.Windows;
using System.Windows.Input;

namespace WpfApp1
{
    class ViewModel
    {
        public ICommand func { get; set; }

        public ViewModel()
        {
            func = new DelegateCommand(
                () =>
                {
                    MessageBox.Show("トリガー発火");
                    return;
                });
        }

        public void EventFunc()
        {
            MessageBox.Show("CallMethodAction でトリガー発火(ViewModelのメソッド)");
        }
    }
}

説明

今回やりたかったのが、MainWindow.xamlの中の「ViewModelのメソッドの呼び方3(コードビハインドの中のメソッドを呼ぶ)」のところ。

<i:EventTrigger EventName="MouseDown">で、TextBlockでマウスが押されたときに、コードビハインドに書かれたメソッド「CodeBehindFunc」を呼んでいる。

この書き方であれば、「EventTrigger」で表現できるイベントであれば、「ViewModelのICommand」「ViewModelクラスのメソッド」「コードビハインドのメソッド」のどれでも呼べる。

EventTriggerのEventNameを変えてやれば、クリックしたときだけでなく、表示時に一回なにかする、とかもできる。

また、サンプルのように、<i:EventTrigger EventName="MouseDown">の中に書いたTriggerActionを継承したクラスのオブジェクト(CallMethodActionやInvokeCommandActionなど)をセットしてやれば、上から順番に実行してくれるので、ViewModelのICommandとコードビハインドのメソッドの両方を実行するとかもできそう。(それが良いかどうかは別問題)

備考

書き終わってから、「コントロールのイベントハンドラを追加して、そこに処理を書けばこんなことしなくて済むのでは?」と思ってしまった。

もしかすると、i:EventTriggerよりも、PropertyChangedEventTriggerを使って、ViewModelのプロパティが変わった時に、コードビハインドのメソッドを呼べる、とかの方が、真価なのかもしれない。

コード

https://github.com/tera1707/WPF-/tree/master/003_EventTrigger2

関連記事

[C#/WPF]ビューモデルからビューのメソッドを呼ぶ
https://qiita.com/tera1707/items/158db72db21b17a1d9c5

【C#/WPF】EventTriggerを使って、Buttonでなくてもクリック時のCommandをかけるようにする
https://qiita.com/tera1707/items/7ecde6e97a19437cbf72

参考

WPF4.5入門 その58「Behavior」
https://blog.okazuki.jp/entry/2014/12/21/205558

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