読者です 読者をやめる 読者になる 読者になる

goodbyeboredworld

気ままに何か書くところ

Excelから値を取り出すAPIをLightNodeで実装する

Excelサイコー!だがしかし…

猫も杓子も業務で使ってるアプリといえばMicrosoftOfficeだしExcelだと思うんですよ。
あらゆる会社でいろんなデータが入力され保存されていると思います。

しかしこのExcelファイルに入力されたデータを別のプログラムで使いたい、となった時に

  • CSV等でテキストにエクスポートする
  • .NET Frameworkを使って操作する

くらいしか選択肢が無いのです。人力でエクスポートは1回ならいいけど繰り返すとなるとめんどい。
かといって.NET FrameworkでCOMを操作して値を取り出す…というプログラムは割りとハードル高い気がする。
もっとさくっと人の手を煩わせることなくHttpRequestを送ったらjsonが返ってくるようなシンプルなAPIがあれば…

それLightNodeでできるよ

github.com
LightNodeは非常に手軽にAPIを実装できるライブラリです。つまり

  • VSTOアドインでExcelのCellの値をjsonにして返すAPIを実装する
  • VSTOアドインでOWINをセルフホストする
  • セルフホストしたOWIN上でLightNodeを使ってAPIを公開する

という手順で割りと簡単にExcelの値を取得できるようになるはず!

APIを考える

http://localhost:{適当なポート番号}/{Excelファイル名}/{シート名}/api/values?startRow=1&startCol=1&endRow=10&endCol=5

みたいなURLで値をとれるようにしたいと思います。

Startupの実装

RequestPathを解析してどのシートが指定されているかを判断し、そのWorksheetオブジェクトをEnvironmentにつっこむMiddlewareを用意します。
雑な実装ですが以下のようになりました。

using System.Linq;
using LightNode.Formatter;
using LightNode.Server;
using Microsoft.Owin;
using Owin;

using Excel = Microsoft.Office.Interop.Excel;

[assembly: OwinStartup(typeof(LightNodeSample.Startup))]

namespace LightNodeSample
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.Use((context, next) =>
            {
                // URLから該当シートを取得するMiddleWare
                var reqPathBase = context.Environment["owin.RequestPathBase"].ToString().Substring(1).Split('/');
                // 少々強引だが許してほしい
                var bookName = reqPathBase[0];
                var sheetName = reqPathBase[1];

                var targetBook = Globals.ThisAddIn.Application.Workbooks.Cast<Excel.Workbook>().FirstOrDefault(x => ThisAddiIn.ExtractExt(x.Name).Item1 == bookName);
                if (targetBook != null)
                {
                    var sheet = targetBook.Worksheets.Cast<Excel.Worksheet>().FirstOrDefault(x => x.Name == sheetName);
                    context.Environment.Add("TargetWorksheet", sheet);
                }

                return next();
            });
            // LightNodeを使う
            app.UseLightNode();
        }

    }
}

※ExtractExtってのは後で出てきます。

続いてAPI本体

using System.Linq;
using System.Net;

using LightNode.Server;
using Newtonsoft.Json;

using Microsoft.Office.Interop.Excel;

namespace LightNodeSample
{
    public class Api : LightNodeContract
    {
        public string Values(int startRow, int startCol, int endRow, int endCol)
        {
            var sheet = this.Environment["TargetWorksheet"] as Worksheet;
            if (sheet == null)
            {
                throw new ReturnStatusCodeException(HttpStatusCode.NotFound);
            }

            Range staCell = sheet.Cells[startRow, startCol];
            Range endCell = sheet.Cells[endRow, endCol];
            var target = sheet.Range[staCell, endCell];

            var values = target.Rows.Cast<Range>()
                .Select(r => r.Columns.Cast<Range>().Select(c => c.Text as string ?? "").ToArray())
                .ToArray();

            return JsonConvert.SerializeObject(values);

        }
    }

}

とまあ特に解説の必要も無い程シンプルな指定されたRangeのTextを集めて配列にするだけのAPIです。

あとは実験用にとりあえずホストしてみましょう。
カスタムリボン上でON/OFF切り替えられるUIがあると良さそうですが
とりあえず開いたら待機するようにします。

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

using Microsoft.Owin.Hosting;
using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;

namespace LightNodeSample
{
    public partial class ThisAddIn
    {
       
        private readonly ConcurrentDictionary<string, IEnumerable<IDisposable>> serverPool = new ConcurrentDictionary<string, IEnumerable<IDisposable>>(); 

        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            
            Excel.AppEvents_Event events = this.Application;
            // とりあえず開いたやつのAPI作る
            events.WorkbookOpen += Application_WorkbookOpen;
            events.NewWorkbook += Application_WorkbookOpen;
        }

        void Application_WorkbookOpen(Excel.Workbook Wb)
        {
            ActivateApi(Wb);
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
            foreach (var server in serverPool.SelectMany(x => x.Value))
            {
                server.Dispose();
            }

            serverPool.Clear();
        }

        public void ActivateApi(Excel.Workbook wb)
        {
            var name = ExtractExt(wb.Name);

            // 存在すれば消す
            IEnumerable<IDisposable> svs;
            if (serverPool.TryRemove(name.Item1, out svs))
            {
                svs.ToList().ForEach(x => x.Dispose());
            }

            serverPool.GetOrAdd(
                name.Item1,
                key => wb.Worksheets.Cast<Excel.Worksheet>()
                    .Select(x =>
                    {
                        var url = string.Format(@"http://localhost:8080/{0}/{1}/", name.Item1, x.Name);
                        return WebApp.Start<Startup>(url);
                    })
                    .ToArray());                
        }

        public static Tuple<string, string> ExtractExt(string source)
        {
            // Workbook.Nameは保存前は拡張子無しで保存後は拡張子付きが返ってくるので雑に吸収
            const string pattern = @"(.+)(\..+)$";

            var match = Regex.Match(source, pattern);
            return match.Success
                ? Tuple.Create(match.Groups[1].Value, match.Groups[2].Value)
                : Tuple.Create(source, "");
        }

        /*

        */
    }
}

細かい突っ込みどころはありそうですがこんな感じで書いたらデバッグ実行します。

f:id:nk9k:20150501172850p:plain
適当に書き込んでChromeでアクセスすると…

f:id:nk9k:20150501172855p:plain
あっさり値が取れました!めでたしめでたし。

まとめ

何はともあれLightNode手軽でスゴいってところが伝わって欲しい感じでした。
VSTOでもOWINセルフホストがすんなり動くってのもスゴいって思いました。
SignalRもすんなり動くっぽいですし作り込み次第でいろいろできそうな可能性ありますね。

僕の私のSQL記法

IDEによる整形サポートがあったりインデントがコードの一部だったりする言語と違って
SQLの書き方って流派がたくさんあるような気がするので自分なりの書き方をまとめてみようと思いました。
ちなみにSQLServerMySQLくらいしかまともに触ってません。Oracleは少しだけ。

キーワードはUPPER CASE

いきなり宗教戦争がおきそうな感じですがSQLって基本的にCase Insensitiveだしシンタックスハイライト無しでもぱっと見の視認性がいい気がするので
SQLのキーワードは全部大文字で書くようにしています。

SELECT * FROM Hoge WHERE Moge = 1;

キーワードのみで改行する

ワンライナーで書けるくらい短いSQLの場合はいいんですが、ある程度長い場合はキーワードのみで改行します。句の対象にインデントをつけます。
以下のような感じです。

SELECT
    *
FROM
    Hoge
WHERE
    Mode = 1
;

句ごとのまとまりがはっきりするので好きです。

インデントは4スペース相当

タブかスペースか問題はこれまた宗教戦争に発展しやすい問題なので置いておきますが、幅はスペース4つ分が望ましいです。

サブクエリ等の ( はキーワードの後ろに置く

C#よりはJavaっぽい感じです。終わりの ) は句のインデントに揃えます。

SELECT
    *
FROM (
    SELECT
        *
    FROM
        Hoge
    WHERE
        Moge = 1
)
;

AND は前置 OR は後置

これは自分でもまだ揺らぎがありますが基本的にはこれです。

SELECT
    *
FROM
    Hoge
WHERE
    Moge = 1
AND Moja = 'もじゃー'
;

SELECT
    *
FROM
    Fuga
WHERE
    Foo = 1 OR
    Bar = 2
;

ANDの後改行しません。インデントがスペース4つ分だとこれで頭が揃うんです。

ASは書かない

別名つけるときのASは基本的にいらないかなーと思ってます。

CASE式

WHEN ◯ THEN ● をカタマリで列挙します

SELECT
    Moge,
    CASE Moja 
        WHEN 'もじゃー' THEN 10
        ELSE 1
    END MojaRatio
FROM
    Hoge
;

SELECT
    Moge,
    CASE 
        WHEN Moja = 'もじゃー' THEN 10
        ELSE 1
    END MojaRatio
FROM
    Hoge
;

とりあえず思いつくところを書いてみました。
SELECT以外のクエリだとまたいろいろありますが基本的には以上のような感じで書いてます。

SourceTreeが重たいならPowerShellを使えばいいじゃない

Windows派ならGUIでしょ!だがしかし…

会社の標準GitクライアントはSourceTreeなのですが
なんだか動作がもっさりしているのが気になるといえば気になるのです。

GUICUIは一長一短だと思うので両方使えるようにしていいとこどりをするのが
幸せになれそう、ってことでCUIでの操作も行えるようにしてみようと思い立ったのが先日。

結論:PowerShell最強だった

WindowsCUIといえば今の時代PowerShell一択なんですよ。
ということで自分がした作業のメモ。

msysgitのインストール

Git for Windows
※SourceTreeの内臓Gitでいけるのかわからなかったのでもしかしたらいらなかったかも

posh-gitのインストール

dahlbyk/posh-git · GitHub
PSGetChocolateyあると捗る

とりあえずこれでPowerShellからGitのコマンド叩ける!
詳しい設定方法とかは他にいくらでも解説してるブログがあるので検索を推奨します。

認証情報の保存

git config --global credential.helper wincred

詳しい設定方法とかは他にいくらでも解説してるブログがあるので検索を推奨します。

※参考 PowerShellGodの神スクリプト

コミットメッセージエディタを変更

git config --global core.editor "使いたいエディタのパス"

詳しい設定方法とかは(ry

作業フォルダをPowerShellで開くショートカットを作る

これは他にいいやり方あるかもしれませんが…

  1. 右クリック→新規作成→ショートカット
  2. 項目の場所にpowershellと入力
  3. 名前を適当につける
  4. できあがったショートカットのプロパティを開く
  5. 作業フォルダーにGitリポジトリのルートを指定

このショートカットを使うとすぐさまgitコマンドが打てる状態でPowerShellが立ち上がります。
※フォントとか色とかカスタマイズ推奨

これでぼちぼちCUIからのGit操作を覚えようと思っています。
コマンドを覚えるのはつらぽよですが
PowerShellはタブで補完効くので最強です。

より柔軟なGridレイアウトをXAMLで実現する為のカスタムコントロールの1例

この記事は XAML Advent Calendar 2014 5日目の記事となります。

XAML Advent Calendar 2014 - Qiita

何をするか

XAMLを使ったレイアウトの基本といえばやはりGridですよね。

Gridコントロールは行と列がいくつあって、子コントロールをそれぞれどの位置に配置する
というのを記述するのが基本の使い方になります。
※Gridの基本的な使い方は割愛します。

モバイルデバイスの普及した昨今UIには
「様々な解像度に対応して適切な表示に可変する」
ことが求められています。
(例えば画面の横幅がある値以上あれば3カラム表示、小さければ2カラムで表示する等)

標準のGridでは行と列の数や子コントロールの表示位置などは1パターンしか設定できません。
条件によって可変にするにはどうしてもC#(or VB)を書く必要があります。
その部分を実装したカスタムコントロールを作って
利用する際はXAMLの定義のみで柔軟なGridを実現できるようにしてみよう、
というのがこの記事の内容です。

柔軟なGridへのアプローチ

実装内容としては

  • 状態を管理するプロパティを作る
  • プロパティの値に応じて(Row|Column)Definitionsをセットする
  • 子のGrid添付プロパティもセットする

だけです。

実装してみたコードは以下になります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace GridSampleApplication
{
    public class FlexibleGrid : Grid
    {
        public FlexibleGrid()
        {
            StateDefinitions = new Dictionary<string, FlexibleStateDefinition>();
        }

        public string FlexState
        {
            get { return (string)GetValue(FlexStateProperty); }
            set { SetValue(FlexStateProperty, value); }
        }

        public static readonly DependencyProperty FlexStateProperty =
            DependencyProperty.Register("FlexState", typeof(string), typeof(FlexibleGrid), 
            new PropertyMetadata("", new PropertyChangedCallback(OnFlexStatePropertyChanged)));

        private static void OnFlexStatePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            var me = sender as FlexibleGrid;
            if (me == null) return;

            me.OnFlexStateChanged(e);
        }
        
        protected virtual void OnFlexStateChanged(DependencyPropertyChangedEventArgs e)
        {
            ChangeState();
        }
        
        private void ChangeState()
        {
            FlexibleStateDefinition targetSetting = null;

            if (string.IsNullOrWhiteSpace(FlexState))
            {
                // Default指定の方法はDefault指定の添付プロパティがあってもいいかもしれない
                var defaultSettings = StateDefinitions.FirstOrDefault();
                targetSetting = defaultSettings.Value;
            }
            else if (StateDefinitions.ContainsKey(FlexState))
            {
                targetSetting = StateDefinitions[FlexState];
            }

            if (targetSetting != null)
            {
                ColumnDefinitions.Clear();
                if (targetSetting.ColumnDefinitions != null)
                {
                    foreach (var col in targetSetting.ColumnDefinitions)
                        ColumnDefinitions.Add(col);
                }

                RowDefinitions.Clear();
                if (targetSetting.RowDefinitions != null)
                {
                    foreach (var row in targetSetting.RowDefinitions)
                        RowDefinitions.Add(row);
                }
            }

            // ChildrenのColとRow
            foreach (var child in Children.OfType<UIElement>())
            {
                var positions = GetPositions(child);
                if (positions == null) continue;
                if (!positions.ContainsKey(FlexState)) continue;

                var position = positions[FlexState];

                SetColumn(child, position.Column);
                SetColumnSpan(child, position.ColumnSpan > 0 ? position.ColumnSpan : 1);
                SetRow(child, position.Row);
                SetRowSpan(child, position.RowSpan > 0 ? position.RowSpan : 1);
            }

        }

        // Dictionaryのキーはx:Keyでセットできます
        public Dictionary<string, FlexibleStateDefinition> StateDefinitions { get; set; }

        // 子に添付プロパティとしてセットできるようにしておく
        public static Dictionary<string, FlexiblePosition> GetPositions(DependencyObject obj)
        {
            var dictionary = (Dictionary<string, FlexiblePosition>)obj.GetValue(PositionsProperty);
            if (dictionary == null)
            {
                dictionary = new Dictionary<string, FlexiblePosition>();
                SetPositions(obj, dictionary);
            }
            return dictionary;
        }

        public static void SetPositions(DependencyObject obj, Dictionary<string, FlexiblePosition> value)
        {
            obj.SetValue(PositionsProperty, value);
        }

        public static readonly DependencyProperty PositionsProperty =
            DependencyProperty.RegisterAttached("PositionsInternal", typeof(Dictionary<string, FlexiblePosition>), typeof(FlexibleGrid), new PropertyMetadata(null));

    }

    public class FlexibleStateDefinition
    {
        public FlexibleStateDefinition()
        {
            RowDefinitions = new ObservableCollection<RowDefinition>();
            ColumnDefinitions = new ObservableCollection<ColumnDefinition>();
        }

        public ObservableCollection<RowDefinition> RowDefinitions { get; set; }
        public ObservableCollection<ColumnDefinition> ColumnDefinitions { get; set; }
    }

    public class FlexiblePosition
    {
        public int Row { get; set; }
        public int Column { get; set; }

        public int RowSpan { get; set; }
        public int ColumnSpan { get; set; }
    }
}

このカスタムGridを使ったXAMLの記述例は以下のようになります。

<Window x:Class="GridSampleApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:me="clr-namespace:GridSampleApplication"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="MainWindow" Height="480" Width="525">
    
    <me:FlexibleGrid x:Name="grdMain" >
        <!-- Gridの行列パターン -->
        <me:FlexibleGrid.StateDefinitions>
            <!-- Default -->
            <me:FlexibleStateDefinition x:Key="Default">
                <me:FlexibleStateDefinition.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition Height="Auto" />
                </me:FlexibleStateDefinition.RowDefinitions>
                <me:FlexibleStateDefinition.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition />
                    <ColumnDefinition Width="Auto" />
                </me:FlexibleStateDefinition.ColumnDefinitions>
            </me:FlexibleStateDefinition>
            <!-- Small -->
            <me:FlexibleStateDefinition x:Key="Small">
                <me:FlexibleStateDefinition.RowDefinitions>
                    <RowDefinition MinHeight="128" />
                    <RowDefinition MinHeight="128"/>
                    <RowDefinition Height="Auto" />
                </me:FlexibleStateDefinition.RowDefinitions>
                <me:FlexibleStateDefinition.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition />
                </me:FlexibleStateDefinition.ColumnDefinitions>
            </me:FlexibleStateDefinition>
            <!-- Large -->
            <me:FlexibleStateDefinition x:Key="Large">
                <me:FlexibleStateDefinition.RowDefinitions>
                    <RowDefinition Height="6*"/>
                    <RowDefinition Height="4*" />
                </me:FlexibleStateDefinition.RowDefinitions>
                <me:FlexibleStateDefinition.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="7*"/>
                    <ColumnDefinition Width="3*" />
                </me:FlexibleStateDefinition.ColumnDefinitions>
            </me:FlexibleStateDefinition>
        </me:FlexibleGrid.StateDefinitions>
        <!-- デザイナ用に定義 ※Defaultと同じ-->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="leftContent" Background="LawnGreen">
            <me:FlexibleGrid.Positions>
                <me:FlexiblePosition x:Key="Default" RowSpan="2" />
                <me:FlexiblePosition x:Key="Small" />
                <me:FlexiblePosition x:Key="Large" RowSpan="2"/>
            </me:FlexibleGrid.Positions>
            <TextBlock Text="左ペイン" Margin="8"/>
        </StackPanel>
        <ScrollViewer x:Name="mainContent" VerticalScrollBarVisibility="Auto" Grid.Column="1">
            <me:FlexibleGrid.Positions>
                <me:FlexiblePosition x:Key="Default" Column="1" RowSpan="2"/>
            </me:FlexibleGrid.Positions>
            <StackPanel Background="Orange">
                <TextBlock Text="長い領域" Margin="8"/>
                <Rectangle Height="300" />
                <TextBlock Text="長い領域 NO OWARI" Margin="8"/>
            </StackPanel>
        </ScrollViewer>
        <StackPanel x:Name="rightContent" Background="LightYellow" Grid.Column="2">
            <me:FlexibleGrid.Positions>
                <me:FlexiblePosition x:Key="Default" Column="2" />
                <me:FlexiblePosition x:Key="Small" Row="1"/>
                <me:FlexiblePosition x:Key="Large" Column="2"/>
            </me:FlexibleGrid.Positions>
            <TextBlock Text="右ペイン" Margin="8" />
        </StackPanel>
        <Grid x:Name="bottomContent" Grid.Row="2" Grid.ColumnSpan="3">
            <me:FlexibleGrid.Positions>
                <me:FlexiblePosition x:Key="Default" Row="2" ColumnSpan="3"/>
            </me:FlexibleGrid.Positions>
            <Border Background="LightBlue">
                <TextBlock Text="下端領域" Margin="8"/>
            </Border>
        </Grid>
    </me:FlexibleGrid>
</Window>

例えばgrdMain.FlexState = "Large"と設定すると下記の抜粋部分がセットされます。

        <!-- RowDefinitionsとColumnDefinitionsはこれが設定される -->
        <me:FlexibleStateDefinition x:Key="Large">
            <me:FlexibleStateDefinition.RowDefinitions>
                <RowDefinition Height="6*"/>
                <RowDefinition Height="4*" />
            </me:FlexibleStateDefinition.RowDefinitions>
            <me:FlexibleStateDefinition.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="7*"/>
                <ColumnDefinition Width="3*" />
            </me:FlexibleStateDefinition.ColumnDefinitions>
        </me:FlexibleStateDefinition>

        <!-- 子の添付プロパティの例 -->
        <StackPanel x:Name="leftContent" Background="LawnGreen">
            <me:FlexibleGrid.Positions>
                <me:FlexiblePosition x:Key="Default" RowSpan="2" />
                <me:FlexiblePosition x:Key="Small" />
                <!-- これが設定される -->
                <me:FlexiblePosition x:Key="Large" RowSpan="2"/>
            </me:FlexibleGrid.Positions>
            <TextBlock Text="左ペイン" Margin="8"/>
        </StackPanel>

FlexState=Default
f:id:nk9k:20141202172401p:plain
FlexState=Small
f:id:nk9k:20141202172409p:plain
FlexState=Large
f:id:nk9k:20141202172414p:plain

XAMLのみで複数パターンのGridレイアウトを設定することができました。

オマケ

せっかくXAMLのみで複数レイアウトを設定できるようになったので
切り替えもXAMLのみで完結できるようなTriggerActionを書いてみました。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Interactivity;

using System.Windows;

namespace GridSampleApplication
{
    public class SetFlexibleGridStateAction : TargetedTriggerAction<FlexibleGrid>
    {

        public SetFlexibleGridStateAction() : base()
        {
            Settings = new ObservableCollection<StateSetting>();
        }

        public ObservableCollection<StateSetting> Settings { get; set; }

        protected override void Invoke(object parameter)
        {
            if (Target == null) return;

            var args = parameter as SizeChangedEventArgs;
            if (args == null) return;

            if (!Settings.Any()) return;

            // 最初に一致したやつ
            var setting = Settings.FirstOrDefault(x => x.IsFit(args.NewSize));
            if (setting == null) return;

            Target.FlexState = setting.StateKey;
        }

    }

    public class StateSetting
    {
        public double? MinWidth { get; set; }
        private double MinWidthOrDefault() { return MinWidth.GetValueOrDefault(0D); }
        public double? MaxWidth { get; set; }
        private double MaxWidthOrDefault() { return MaxWidth.GetValueOrDefault(double.PositiveInfinity); }
        
        public double? MinHeight { get; set; }
        private double MinHeightOrDefault() { return MinHeight.GetValueOrDefault(0D); }
        public double? MaxHeight { get; set; }
        private double MaxHeightOrDefault() { return MaxHeight.GetValueOrDefault(double.PositiveInfinity); }

        public string StateKey { get; set; }

        public bool IsFit(Size size)
        {
            return (
                MinWidthOrDefault() <= size.Width &&
                MaxWidthOrDefault() >= size.Width &&
                MinHeightOrDefault() <= size.Height &&
                MaxHeightOrDefault() >= size.Height
            );
        }

    }

}

Windowに引っ掛けてやればOKです。

<Window x:Class="GridSampleApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:me="clr-namespace:GridSampleApplication"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        Title="MainWindow" Height="480" Width="525" Loaded="Window_Loaded" Closing="Window_Closing">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SizeChanged">
            <me:SetFlexibleGridStateAction TargetObject="{Binding ElementName=grdMain}" >
                <me:SetFlexibleGridStateAction.Settings>
                    <me:StateSetting MaxHeight="360" MaxWidth="480" StateKey="Small"/>
                    <me:StateSetting MinHeight="360" MaxHeight="600" MinWidth="480" MaxWidth="800" StateKey="Default"/>
                    <me:StateSetting MinHeight="600" MinWidth="800" StateKey="Large"/>
                </me:SetFlexibleGridStateAction.Settings>
            </me:SetFlexibleGridStateAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>

</Window>

※このサイズ指定値は適当です

ちょっとしたカスタムコントロールを作ることで
XAMLをより強力にできる余地があるのも
XAMLの魅力と言えるのではないでしょうか。

DisposableThreadLocalを.NET4.0環境で

neue cc - 並列実行とSqlConnection

.NET4.5環境であればのいえ先生の記事ようにすればいいし
ParallelExのやつもアリですが
VB環境だとラムダ式の記述がアレなせいでいまいちな感じなので
やはりDisposableThreadLocalを用意したいと思いましたのでやりました。

Imports System.Threading

Public Class DisposableThreadLocal(Of T As IDisposable)
    Implements IDisposable

    Private threadLocal As ThreadLocal(Of T)

    Private pool As New List(Of T)

    Sub New(valueFactory As Func(Of T))
        Dim wrapper = _
            Function()
                Dim value = valueFactory.Invoke()
                pool.Add(value)
                Return value
            End Function
        threadLocal = New ThreadLocal(Of T)(wrapper)
    End Sub

    Public ReadOnly Property IsValueCreated As Boolean
        Get
            Return threadLocal.IsValueCreated
        End Get
    End Property

    Public ReadOnly Property Value As T
        Get
            Return threadLocal.Value
        End Get
    End Property

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 重複する呼び出しを検出するには

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: マネージ状態を破棄します (マネージ オブジェクト)。
                pool.ForEach(Sub(x) x.Dispose())
                threadLocal.Dispose()
            End If

            ' TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下の Finalize() をオーバーライドします。
            ' TODO: 大きなフィールドを null に設定します。
        End If
        Me.disposedValue = True
    End Sub

    ' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(disposing As Boolean) に記述します。
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

とりあえず動きそうではあります。

Dispose時にConsole.WriteLineするクラスで試します。

Public Sub DisposableThreadLocalテスト()

    Dim sync As New Object
    Dim counter As Integer = 0

    Dim factory = _
        Function()
        SyncLock sync
            counter += 1
        End SyncLock
        Return New DisposableTest() With {.Message = counter.ToString() & "CreatedAt:" & DateTime.Now.ToString("mm.ss.ffff")}
    End Function

    Using threadLocal = New DisposableThreadLocal(Of DisposableTest)(factory)

        Parallel.For(1, 100,
                        Sub(i)
                        Thread.Sleep(i)
                        Console.WriteLine(threadLocal.Value.Message)
                    End Sub)

    End Using
End Sub

' Disposed:1CreatedAt:33.10.6715
' Disposed:2CreatedAt:33.10.6826
' Disposed:3CreatedAt:33.10.6946
' Disposed:4CreatedAt:33.10.7067
' Disposed:5CreatedAt:33.10.7187
' Disposed:6CreatedAt:33.10.7309
' Disposed:7CreatedAt:33.10.7431
' Disposed:8CreatedAt:33.10.7553
' Disposed:9CreatedAt:33.10.7674

直列だと5050ミリ秒以上かかるはずですが
自分の環境だと720ミリ秒程で終了します。

SqlConnectionに使用するとどうなるかなぁというところです。

Silverlight.UnitTest時MVVMLightのMessengerのSendで例外

MVVMLightのMessengerでSendが適切に行われるかテストしたいので以下のように書きました。

<TestMethod()>
Public Sub MessengerのSendテスト()

    Dim result As Boolean
    Messenger.Default.Register(Of String)(Me, Sub(msg) result = True)

    Messenger.Default.Send("Test")

    Assert.AreEqual(True, result)

End Sub

これ走らせるとMethodAccessExceptionで死にます。
MethodAccessExceptionってことはパブリックなメソッドにすればいいのかと思い
以下のように書いたらパスしました。

Private result As Boolean
<TestMethod()>
Public Sub MessengerのSendテスト()

    Messenger.Default.Register(Of String)(Me, AddressOf MessegeRecieve)

    Messenger.Default.Send("Test")

    Assert.AreEqual(True, result)

End Sub

Public Sub MessegeRecieve(msg As String)
    result = True
End Sub

えー激烈にイケてないですが…

リフレクションと式木で別インスタンスの同名プロパティにアサインする

同じ名前で同じ型のプロパティがあれば全てアサインするコードを書いてみました。

Imports System.Collections.Concurrent
Imports System.Reflection
Imports System.Linq.Expressions

Public Class PropertyAssigner

    Private Class AssignerKey
        Public Property FromType As Type
        Public Property ToType As Type

        Private Shared eqc As IEqualityComparer(Of AssignerKey)
        Public Shared ReadOnly Property EqualityComparer() As IEqualityComparer(Of AssignerKey)
            Get
                If eqc Is Nothing Then
                    ' IEqualityComparer(Of T)の簡易実装
                    eqc = New EqualityComparer(Of AssignerKey)(
                        Function(x, y) (x.FromType = y.FromType) AndAlso (x.ToType = y.ToType),
                        Function(x) x.FromType.GetHashCode() Xor x.ToType.GetHashCode())
                End If
                Return eqc
            End Get
        End Property
    End Class

    Private Shared cache As New ConcurrentDictionary(Of AssignerKey, Action(Of Object, Object)) _
        (AssignerKey.EqualityComparer)

    Public Shared Function TryAssign(Of TFrom, TTo)([from] As TFrom, [to] As TTo) As Boolean

        Try
            Dim fromType = [from].GetType()
            Dim toType = [to].GetType()

            Dim key = New AssignerKey() With {.FromType = fromType, .ToType = toType}

            Dim assignMethod = cache.GetOrAdd(key,
                Function(k)
                    Dim fromProps = fromType.GetProperties().
                        Where(Function(x) x.CanRead)

                    Dim toProps = toType.GetProperties().
                        Where(Function(x) x.CanWrite)

                    Dim assignMethods = toProps.
                        Select(Function(x) New With {
                                   .From = fromProps.FirstOrDefault( _
                                       Function(y) y.Name = x.Name AndAlso y.PropertyType = x.PropertyType),
                                   .To = x
                               }).
                        Where(Function(x) x.From IsNot Nothing).
                        Select(Function(x) CreateAssignExpression(Of TFrom, TTo)(x.From, x.To)).
                        ToList()

                    Return If(assignMethods.Count = 0,
                              New Action(Of Object, Object)(Sub(x, y) Return),
                              CreateAllInvokeMethod(assignMethods))

                End Function)

            assignMethod.Invoke([from], [to])

            Return True

        Catch ex As Exception
            ' todo:何かログ
            Return False
        End Try

    End Function

    Public Shared Function CreateAssignExpression(Of TFrom, TTo) _
        (fromMember As MemberInfo, toMember As MemberInfo) As Expression(Of Action(Of TFrom, TTo))

        Dim [to] = Expression.Parameter(GetType(TTo))
        Dim left = Expression.MakeMemberAccess([to], toMember)

        Dim [from] = Expression.Parameter(GetType(TFrom))
        Dim right = Expression.MakeMemberAccess([from], fromMember)

        Dim body = Expression.Assign(left, right)

        Return Expression.Lambda(Of Action(Of TFrom, TTo))(body, [from], [to])

    End Function

    Public Shared Function CreateAllInvokeMethod(Of TFrom, TTo) _
        (methods As IEnumerable(Of Expression(Of Action(Of TFrom, TTo)))) As Action(Of Object, Object)

        Dim [from] = Expression.Parameter(GetType(Object))
        Dim fromConv = Expression.Convert([from], GetType(TFrom))
        Dim [to] = Expression.Parameter(GetType(Object))
        Dim toConv = Expression.Convert([to], GetType(TTo))

        Dim body = Expression.Block(methods.Select(Function(x) Expression.Invoke(x, fromConv, toConv)))

        Return Expression.Lambda(Of Action(Of Object, Object))(body, [from], [to]).Compile()

    End Function

End Class

肝心のパフォーマンス測ってないし
キャッシュのクリアをどうするとかありますがとりあえず動きます。
個人的には式木も一行単位で区切っていくと書きやすいんです。
式木らしさ無いですけど。