愚者の経験

「また今度」はほとんどこない

カテゴリーアーカイブ: Silverlight

Webアプリケーション

Silverlightはしばらく様子見しようと思います。
Webアプリケーションの方に手を伸ばしてみます。

さしあたってPHP、AJAX、ASP.Net、Razorあたりに見当をつけていきます。

ココらへんの技術はたくさんあって基幹業務系のアプリの作成にはどれをやればいいのか
まったくわかりません…とりあえず広範に見ていくことにします。

ブログの内容も雑多になりそうです。

広告

SilverlightのローカルDB「Sterling」を使ってみる

前の投稿でも紹介しました「Sterling」を使ってみます。
C#の少し勉強(と言っても本買ってしたわけでなく実験的にコーディングしただけ)したので
当時わからなかったことでも今は若干理解出来ます。「ラムダ式」はかなり苦手ですが。

参考URL:http://www.slideshare.net/odashinsuke/silverlightwp7-db

やはり「こう書けばできる」より理解できている方が色々応用できます。

まず保存するデータクラス(「エンティティ」というらしい)を定義します。
Accessでいうところのテーブルデザインです。

using System;

namespace Sterling
{
    public class Person
    {
        public Guid ID { get; set; }
        public int Number { get; set; }
        public string Name { get; set; }
    }
}

publicのみ保存対象です。いつもの「Person」クラスです。
「Sterling」はクラスを直接保存するようなデータベースで「RDB」ではなく「OODB」です。

次にデータベースクラスを定義します。
データベースクラスはBaseDatabaseInstance』を継承し「RegisterTables」メソッドをoverrideして
メソッド内で『ITableDefinition』の『List』を返します。

『ITableDefinition』 は「CreateTableDefinition」メソッドで作成します。
「CreateTableDefinition」メソッドの引数に指定したデータクラスを保存できるようになります。

using System;
using System.Collections.Generic;
using Wintellect.Sterling.Database;

namespace Sterling
{
    public class Database : BaseDatabaseInstance
    {
        //インデックス名
        public const string IX_Person_ID = “IX_Person_ID”;
        public const string IX_Person_Number_Name = “IX_Person_Number_Name”;

        protected override List RegisterTables()
        {
            return new List()
            {
                //データクラスを指定(ラムダ式で主キーのプロパティ指定)
                CreateTableDefinition(e=>e.Number)
                    //インデックスを指定
                    .WithIndex
                        //インデックス名,ラムダ式でインデックスの値を返す
                        (IX_Person_ID,e=>e.ID)
                    //インデックスを指定(2個目)
                    .WithIndex
                        //インデックス名,ラムダ式でインデックスの値を返す
                        (IX_Person_Number_Name,e=>new Tuple(e.Number,e.Name))
            };
        }
    }
}

ラムダ式は「パッと書け」と言われると無理です。

インデックスが不要であれば.WithIndex以下の文は要りません。
あとインデックスと言っても「重複なし」だとか指定できるわけはないです。
あくまでメモリ上に持つ値の指定です。※まだ使いたてなのでおそらくですが…

現段階ではGuidはKeyに指定するとエラーになりました。参考URLではKeyに指定できると
書いてあるのでやり方が間違っているかもしれません。

CreateTableDefinition(e=>e.ID)←これで実行時にエラー

また一つのインデックスで指定できる列は2つまでです。データの問い合わせではインデックスは
一つずつしか使用できないので(おそらく)実際に使うとなると考えて作る必要があります。

ここでまた勉強です。『Tuple』クラスは値をひとつにまとめたクラスになります。
最高で8つ指定可能で、Tuple.Item1みたいな形で要素にアクセスします。

実際にこれを使ってみます。
データの追加とDatagridに表示をやっています。
「MainPage.xaml」のコードビハインド

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Wintellect.Sterling;
using Wintellect.Sterling.Database;
using Wintellect.Sterling.IsolatedStorage;

namespace Sterling
{
    public partial class MainPage : UserControl
    {
        private SterlingEngine _engine;
        private ISterlingDatabaseInstance _db;

        public MainPage()
        {
            InitializeComponent();
            _engine = new SterlingEngine();
            _engine.Activate();
            _db = _engine.SterlingDatabase.RegisterDatabase(new IsolatedStorageDriver());
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            //Linqで問い合わせ(インデックスを使用する場合は(インデックス名)
            var grid =from p in _db.Query(Database.IX_Person_Number_Name)

                        //where p.Index.Item1 == 1
                        //インスタンス(実際の値)はLazyValue.Valueにある

                        select p.LazyValue.Value;
            dataGrid1.ItemsSource = grid;
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            //インスタンス追加
            _db.Save(new Person() { ID = Guid.NewGuid(), Number = int.Parse(textBox1.Text), Name = (string)textBox2.Text });
            //保存
            _db.Flush();
        }
    }
}

「MainPage.xaml」

<UserControl x:Class="Sterling.MainPage"
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation&#8221;
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml&#8221;
    xmlns:d=”http://schemas.microsoft.com/expression/blend/2008&#8243;
    xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006&#8243;
    xmlns:my=”clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data”          
    mc:Ignorable=”d”
    d:DesignHeight=”600″ d:DesignWidth=”800″>
   
       
       
       
       
       
   

Queryメソッドでインデックスを指定するために「インデックスは一つしか使えない」と判断しました。
最高でもKeyとあわせて3列しか指定できないのかもしれません。Tuple入れ子とかできる?

また保存についてはFlashメソッドを呼んでやる必要があります。

これからもいろいろ調べていきます。
もしSterling知っている人がいたらいろいろ教えて欲しいですm(__)m

DataGridにRecordsetのデータを表示する 1

参考URL:http://erikej.blogspot.com/2010/02/access-local-sql-compact-database-from.html

上記リンクを参考(ほとんどそのまんまなところも)にラッパークラスを書いてきたが、
AdoDataReaderの部分で詰まっています。
これは「Reader」なのでDataGridに表示まで出来ますが、そこで編集ができないです。
DataGrid上で編集するためにはDictionaryの入れ子でなく、列のプロパティを持ったクラスを
コレクションにして渡す必要がある(はず)ので、MVVMでいうViewModel(Modelかも)クラスを作って
1行ずつをViewModelクラスに変換しなくてはいけません。(これなんとかならないかな…)
さらにViewModelクラスでの変更をRecordsetに反映する必要もありますので…
考えただけでも頭が痛いです。

DataGrid データ表示3 入力の検証

System.ComponentModel.DataAnnnotationsにある各検証属性が便利!

using System.ComponentModel.DataAnnotations;

~~~~~~~~

        private int? _number;
        [Required]
        public int? Number
        {
            get { return _number; }
            set
            {
                if (_number == value) return;
                _number = value;
                OnPropertyChanged(“Number”);
            }
        }

プロパティの直上に[Required]と入れるだけで空白だったらエラー表示してくれます。
画期的ですね。使わない手はないです。

しかし無理矢理最下行に新規入力用の空行を追加するとなると

            var persons = new ObservableCollection();
            persons.Add(new Person(1, “佐藤”));
            persons.Add(new Person(2, “山田”));
            persons.Add(new Person(3, “小林”));
            persons.Add(new Person(4, “鈴木”));
            persons.Add(new Person());                 ←入力用の空行

みたいに最後に空のクラスを追加しなければなりませんが、
検証属性はそこにも当然かかるわけでフォーカスを入れた瞬間にエラーが表示されます。
別に気にしなくても良さげなんですが「今から入力するのに…」と思わなくもないので解決策を
探ってみます。

DataGrid データ表示 2

前回の続き
前回はPersonクラスのプロパティの変更を反映しました。
今回は入力の確定とキャンセルを実装してみます。

編集のコミットやキャンセルを行うには
[System.ComponentModel]の「IEditableObject」インターフェイスを実装すればいいそうです。
参考URL1:http://msdn.microsoft.com/ja-jp/library/system.windows.controls.datagrid(v=vs.95).aspx
参考URL2:http://msdn.microsoft.com/ja-jp/library/system.componentmodel.ieditableobject(v=vs.95).aspx

早速実装してみます。

using System;
using System.ComponentModel;

namespace test
{
    public class Person : INotifyPropertyChanged, IEditableObject
    {
        Constructors

        #region Properties

        private int _ID;
        public int ID
        {
            get { return _ID; }
            set
            {
                if (value == _ID) return;
                _ID = value;
                OnPropertyChanged(“ID”);
            }
        }

        private string _Name;
        public string Name
        {
            get { return _Name; }
            set
            {
                if (value == _Name) return;
                _Name = value;
                OnPropertyChanged(“Name”);
            }
        }

        private int _Age;
        public int Age
        {
            get { return _Age; }
            set
            {
                if (value == _Age) return;
                OnPropertyChanged(“Age”);
                _Age = value;
            }
        }

        private string _Address;
        public string Address
        {
            get { return _Address; }
            set
            {
                if (value == _Address) return;
                OnPropertyChanged(“Address”);
                _Address = value;
            }
        }

        #endregion

        #region PropertyChangedMembers
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
                if (_dirty == false) _dirty=true;
            }
        }
        #endregion

        #region IEditableObjectMembers
        private Person _backup;
        private bool _dirty;
        public void BeginEdit()
        {

            if (_dirty == true) return;
            if (_backup == null) _backup = this.MemberwiseClone() as Person;
            else _dirty = true;

        }

        public void CancelEdit()
        {
            if (_dirty == false) return;
            _dirty = false;
            _ID = _backup.ID;
            _Name = _backup.Name;
            _Age = _backup.Age;
            _Address = _backup.Address;
        }

        public void EndEdit()
        {
            if (_dirty == false) return;
            _dirty = false;
            _backup = null;
            System.Windows.MessageBox.Show(“編集完了!”);
        }
        #endregion
    }
}

前回のINotifyPropertyChangedインターフェイスの変更を「ID」にのみ適用していましたが、
今回はすべてのプロパティに同じく適用しています。

なお、変更点がない部分はある程度省略しています。下線部のあるもの

        Constructors

        #region Constructor

        public Person(int id, string name, int age, string address)
        {
            _ID = id;
            _Name = name;
            _Age = age;
            _Address = address;
        }
        #endregion

のように区分けすることにします。「#region」は細かい機能ですが便利ですね。

それでは本題の変更箇所を見ましょう。

        #region IEditableObjectMembers
        private Person _backup;
        private bool _dirty;
        public void BeginEdit()
        {

            if (_dirty == true) return;
            if (_backup == null) _backup = this.MemberwiseClone() as Person;
            else _dirty = true;

        }

        public void CancelEdit()
        {
            if (_dirty == false) return;
            _dirty = false;
            _ID = _backup.ID;
            _Name = _backup.Name;
            _Age = _backup.Age;
            _Address = _backup.Address;
        }

        public void EndEdit()
        {
            if (_dirty == false) return;
            _dirty = false;
            _backup = null;
            System.Windows.MessageBox.Show(“編集完了!”);
        }
        #endregion

「IEditableObject」インターフェイスが実装するのは
「BeginEditメソッド」「CancelEditメソッド」「EndEditメソッド」の3つです。

それぞれ動作するタイミングは名前の通り…では後で自分が後悔するので。
「BeginEdit」→入力時…ではなく「セルにフォーカスが入り、入力できるようになった」時
「CancelEdit」→「[Esc]キー押下で行の変更をキャンセルした」時
「EndEdit」→「入力後に他の行へ移動する」または「入力確定後の[Enter]キー押下」時

「BeginEdit」では行が編集される前にprivate変数の「_backup」に
自分自身をコピーしています。

        if (_backup == null) _backup = this.MemberwiseClone() as Person;

そして「CancelEdit」で「_backup」から元の値を復元します。

            _ID = _backup.ID;
            _Name = _backup.Name;
            _Age = _backup.Age;
            _Address = _backup.Address;
コードのサンプルでは
            this.ID = _backup.ID;
            this.Name = _backup.Name;
            this.Age = _backup.Age;
            this.Address = _backup.Address;

のように書いてあります。
素人考えですがこれでは各プロパティのsetterが動きINotifyPropertyChangedも働きますので
それを嫌って各プロパティのprivate変数に直接代入しています。

さらに「BeginEdit」の発動タイミングが気に入らないため(笑)、
編集フラグである「_dirty」をtrueにするタイミングを「OnPropertyChanged」内にいれています。

私の勝手なコーディングですので、もしかしたらバグがあるかもしれません。

これで[Esc]キーによる行の編集キャンセルと「編集」の確定イベントを取ることが出来ました。

[Esc]キー動作は画像では
わかりにくいので省略。

DataGrid データ表示 番外編

いきなり番外編です。
今までDataGridに表示させるデータにPersonクラスを用意しましたが、
別になくてもできます。

参考URL:http://d.hatena.ne.jp/hate666komei/20110127/1296111879
今のところ「編集できない」「列をクリックしてソートできない」制限がありますが、
以下のようにしてもデータを表示することができます。

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            var datarows = new ObservableCollection<Dictionary>();
            for (int i = 0; i < 4; i++)
            {
                var row = new Dictionary();
                row.Add(“ID”, i);
                switch(i)
                {
                    case 0:
                        row.Add(“Name”,”小林”);
                        row.Add(“Age”, 25);
                        row.Add(“Address”, “東京都”);
                        break;
                    case 1:
                        row.Add(“Name”,”佐藤”);
                        row.Add(“Age”, 32);
                        row.Add(“Address”, “京都府”);
                        break;
                    case 2:
                        row.Add(“Name”,”鈴木”);
                        row.Add(“Age”, 19);
                        row.Add(“Address”, “埼玉県”);
                        break;
                    case 3:
                        row.Add(“Name”,”吉田”);
                        row.Add(“Age”, 40);
                        row.Add(“Address”, “三重県”);
                        break;
                }
                datarows.Add(row);
            }

            datagrid1.AutoGenerateColumns = false;
            foreach (string colname in datarows[0].Keys)
            {
                var col = new DataGridTextColumn();
                col.Header = colname;
                col.Binding = new Binding(“[” + colname + “]”);
                datagrid1.Columns.Add(col);
            }

            datagrid1.ItemsSource = datarows;
        }
    }

C#でVBAの「select case」を利用するには「switch」を利用すればいいようです。
本当は「Choose」関数のように値を返すメソッドがあればよかったのですが
見つけられませんでした。

行データをDictionayとし「Key」に列名、「Value」に値をAddし、
DataGridの列のバインドを定義することで表示が可能になります。
このやり方の利点は列数が可変であることです。

DataGrid データ表示 1

前回の続き
DataGridに新規行を追加するプロパティはないことがわかりました。
自力でコードを書くことになるとしてもまだ他にも必要な機能はあると思います。
先にそちらの方から勉強していきます。

まずはデータ変更の機能です。
その前にPersonクラスを少し変更します。サンプルとして列数が少ないので増やします。

using System;

namespace test
{
    public class Person
    {
        public Person(int id,string name,int age,string address)
        {
            ID = id;
            Name = name;
            Age = age;
            Address = address;
        }
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }
    }
}

これで「ID」「Name」「Age」「Address」の4列です。

プロパティの変更を自動的に反映するには
[System.ComponentModel]の「INotifyPropertyChanged」インターフェイスを実装すればいいそうです。
参考URL1:http://msdn.microsoft.com/ja-jp/library/system.windows.controls.datagrid(v=vs.95).aspx
参考URL2:http://msdn.microsoft.com/ja-jp/library/system.componentmodel.inotifypropertychanged(v=vs.95).aspx

では実装してみます。

using System;
using System.ComponentModel;
using System.Windows;

namespace test
{
    public class Person : INotifyPropertyChanged
    {
        public Person(int id,string name,int age,string address)
        {
            ID = id;
            Name = name;
            Age = age;
            Address = address;
        }

        private int _ID;
        public int ID
        {
            get { return _ID; }
            set {
                if (value == _ID) return;
                _ID = value;
                OnPropertyChanged(“ID”);
            }
        }
     
        public string Name { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
                MessageBox.Show(“「” + propertyname + “」が変更された!”);
            }
        }
    }
}

結果的にはsetterで
「value(入力値)と現在地を比較して、違っていたら、「OnPropertyChanged」を呼ぶ」です。

DataGridで行追加は直接できない?

SilverlightのDataGridにデータを表示させたいときは

「ItemSource」プロパティにIEnumerable実装に設定(ほとんどObservebleCollection?)ということで
適当に実験してました。
using System;

namespace test
{
    public class Person
    {
        public Person(int id,string name)
        {
            ID = id;
            Name = name;
        }
        public int ID { get; set; }
        public string Name { get; set; }
    }
}

ほんとにかなり適当です。

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            ObservableCollection persons = new ObservableCollection();
            persons.Add(new Person(0, “小林”));
            persons.Add(new Person(1, “佐藤”));
            datagrid1.DataContext = persons;
        }

これで出ます…が、Accessでいうところの「レコードセレクタ」がありませんね。
違和感あるので(笑)つけましょう。

            datagrid1.HeadersVisibility = DataGridHeadersVisibility.All;

これでつきます…が、新規行の追加するところがありませんね。
違和感(略)

…つけられません。

参考URL:http://msdn.microsoft.com/ja-jp/library/system.componentmodel.newitemplaceholderposition(v=vs.95).aspx
MSDNの資料によるとWPFの「NewItemPlaceholderPosition」列挙体」には「None」「AtBeginning」「AtEnd」の3種ありますが、Silverlightには「None」しかありません。
※インテリセンスには「None」に加えて「AtEnd」が出ます!!非常に紛らわしいです。

どうやったらつくのか色々調べましたがわかりません。
Personクラスでなんとかするしかないのでしょうか…。

Silverlight 5 リリース!?

SDK(JPN)もあります。
参考URL:http://msdn.microsoft.com/ja-jp/silverlight/bb187452
こんなページあったかな?

でもVisual Studio 2010のアドオンはSP1用しかないです。
この前SP1入れて色々してたらいつの間にか「Silverlight 4」の開発ができなくなって入れなおした経緯があるのでSP1は避けてたんですが…
自分は「Silverlight 4 Tools for Visual Studio 2010」が内蔵されているあたりが怪しいと思いました。
SP1入れる前にこれ入れてた人はどうするのが正しいのかわかりません。

とりあえず今のところ安定してるのは「Silverlight X SDK」だけを入れることです。
これだけ入れていればなんとなくVS2010でも変な動きはありません。

とにかく「Silverlight 5」出ました!期待大です(?)。
個人的にはDataGridが使いやすくなっていると有りがたいです。
バインドの仕方や更新、データの追加用の行などわからないことだらけです。

SilverlightのADODB.Recordsetラッパー

引き続きADODB.Recordsetオブジェクト等。

完全に自信なし。

using System;
using System.Runtime.InteropServices.Automation;

namespace SLADO.Data
{
    public class Recordset : IDisposable
    {
        internal dynamic rs;
        //コンストラクタ
        public Recordset(Connection connection)
        {
            rs = AutomationFactory.CreateObject(“ADODB.Recordset”);
            connection.Open();
            rs.ActiveConnection = connection.cn;
            Fields = new Fields(this);
        }
        //コンストラクタ(オーバーロード)
        internal Recordset(dynamic recordset)
        {
            rs = recordset;
            Fields = new Fields(this);
            Fieldset();
        }

        //フィールドコレクション
        public Fields Fields { get; private set; }
     
        //フィールドのセット
        private void Fieldset()
        {
            if (!EOF && rs.Fields != null && rs.Fields.Count > 0)
            {
                int FieldsCount = rs.Fields.Count;
                for (int i = 0; i < FieldsCount; i++)
                {
                    Fields.Append(i);
                }
            }
        }

        //レコード数の取得
        public int RecordCount
        {
            get { return rs.RecordCount; }
        }

        //BOFの取得
        public bool BOF
        {
            get { return (bool)rs.BOF; }
        }

        //EOFの取得
        public bool EOF
        {
            get { return (bool)rs.EOF; }
        }

        //Accessと同じくrecordset()でフィールドにアクセスする。
        public Field this[int index]
        {
            get { return Fields[index]; }
        }
        //Accessと同じくrecordset()でフィールドにアクセスする。(オーバーロード)
        public Field this[string name]
        {
            get { return Fields[name]; }
        }

        //SQLを実行する(結果を返すSQLステートメント)
        public void ExecuteQuery(string source)
        {
            rs.Open(source);
            Fieldset();
        }

        //SQLを実行する(結果を返すSQLステートメント)(Commandオブジェクトのオーバーロード)
        public void ExecuteQuery(Command source)
        {
            rs.Open();
            Fieldset();
        }

        public void Dispose()
        {
            rs.Close();
            rs = null;
        }
    }
}

Fieldsコレクション。敷居が高すぎた…orz

using System;
using System.Collections.ObjectModel;

namespace SLADO.Data
{
    public class Fields : KeyedCollection
    {
        internal Recordset rs;

        internal Fields(Recordset recordset)
        {
            rs = recordset;
        }

        public void Append(int index)
        {
            this.Add(new Field(rs.rs.Fields(index)));
        }

        protected override string GetKeyForItem(Field item)
        {
            return item.Name;
        }
    }
}

Fieldオブジェクト。必要そうなプロパティのみ実装しました。

using System;
using System.Runtime.InteropServices.Automation;

namespace SLADO.Data
{
    public class Field : IDisposable
    {
        internal dynamic fld;

        //コンストラクタ
        internal Field(dynamic field)
        {
            fld = field;
        }

        //フィールド名の取得
        public string Name
        {
            get { return fld.Name; }
        }

        //値の取得と設定
        public object Value
        {
            get { return fld.Value; }
            set { fld.Value = value; }
        }

        //前回値の取得
        public object OriginalValue
        {
            get { return fld.OriginalValue; }
        }

        //競合時の値の取得
        public object UnderlyingValue
        {
            get { return fld.UnderlyingValue; }
        }

        //データ型の取得
        public AdoDataType Type
        {
            get { return (AdoDataType)fld.Type; }
        }
        public void Dispose()
        {
            fld = null;
        }

    }
}

いよいよ使ってみます。ドキドキです。