2014年3月29日土曜日

INotifyPropertyChangeとINotifyDataErrorInfoの実装方法

 .Net技術者であればViewModelを実装するときに必ず書かなくてはいけないINotifyPropertyChangeプロパティ。出来れば書きたくない、.netが勝手にやってくれてもいいんだよと思う部分を少しでも楽にならないかと考えてみた。

まずは、データーフローから考えてみると
値の変更→データチェック→通知
 OK => INotifyPropertyChange
 NG => INotifyDataErrorInfo
という流れで実装する場合、

set {
    if (validation(value)) {
        //INotifyPropertyChange
    }else{
        //INotifyDataErrInfo
    }
}
この辺りは共通処理になるからここをModelBaseクラスという形で実装しておき、
あとはSetProperty関数を用いて上記処理を処理させようというアプローチである。
過去にもModelがらみの記事に書いた
MongoDBと接続、もしくはJSonファイルに出力する方法にも使えるようにしておいた。


実装方法はModelBaseクラス内でNotify関連を実装してそれを任意のModelクラスに継承するやり方。

Imports System.ComponentModel
Imports MongoDB.Driver
Imports MongoDB.Driver.Builders
Imports MongoDB.Driver.Linq
Imports MongoDB.Bson
Imports MongoDB.Bson.Serialization.Attributes

'http://blog.micic.ch/net/easy-mvvm-example-with-inotifypropertychanged-and-inotifydataerrorinfo
Public Class ModelBase : Implements INotifyPropertyChanged, INotifyDataErrorInfo
#Region "data error"
    Private _errors As New Dictionary(Of String, List(Of String))
    Public Event ErrorsChanged(sender As Object, e As DataErrorsChangedEventArgs) Implements INotifyDataErrorInfo.ErrorsChanged
    Public Function GetErrors(propertyName As String) As IEnumerable Implements INotifyDataErrorInfo.GetErrors
        If _errors.ContainsKey(propertyName) Then
            Return _errors(propertyName)
        End If
        Return Nothing
    End Function
    Public ReadOnly Property HasErrors As Boolean Implements INotifyDataErrorInfo.HasErrors
        Get
            Return 0 < _errors.Count
        End Get
    End Property
    Public ReadOnly Property IsValid As Boolean
        Get
            Return Not HasErrors
        End Get
    End Property
    Private Sub AddError(propertyName As String, errmsg As String)
        If Not _errors.ContainsKey(propertyName) Then
            _errors(propertyName) = New List(Of String)
        End If
        If Not _errors(propertyName).Contains(errmsg) Then
            _errors(propertyName).Add(errmsg)
            RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
        End If
    End Sub
    Private Sub RemoveError(propertyName As String)
        If _errors.ContainsKey(propertyName) Then
            _errors.Remove(propertyName)
            RaiseEvent ErrorsChanged(Me, New DataErrorsChangedEventArgs(propertyName))
        End If
    End Sub
#End Region
#Region "Property changed"
    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
#End Region
#Region "Set Property"
    Private _lock As New Object()
    Protected Delegate Function DlgCheckValidation() As String
    Protected Sub SetProperty(Of T)(propertyName As String, ByRef field As T, value As T, func As DlgCheckValidation)
        If Not EqualityComparer(Of T).Default.Equals(field, value) Then
            SyncLock _lock
                Dim errmsg = ""
                If Not IsNothing(func) Then
                    errmsg = func()
                End If
                If "" = errmsg Then
                    field = value
                    RemoveError(propertyName)
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
                Else
                    AddError(propertyName, errmsg)
                End If
            End SyncLock
        End If

    End Sub
#End Region
End Class



<BsonIgnoreExtraElements>
Public Class AddressModel : Inherits ModelBase
    <BsonId>
    <DisplayName("ID")>
    Public Property _id As BsonObjectId

    Private _name As String
    Private _birthday As Date
    Private _emailAddress As List(Of EMailModel)

    <DisplayName("名前")>
    Public Property name As String
        Get
            Return _name
        End Get
        Set(value As String)
            SetProperty("name", _name, value,
                Function() As String
                        If "abc" = value Then
                            Return "abc not allowed"
                        End If
                        Return ""
                    End Function)
        End Set
    End Property

    Public Property Birthday As Date
        Get
            Return _birthday
        End Get
        Set(value As Date)
            SetProperty("Birthday", _birthday, value, Nothing)
        End Set
    End Property
    Public Property EMailAddress As List(Of EMailModel)
        Get
            Return _emailAddress
        End Get
        Set(value As List(Of EMailModel))
            SetProperty("EMailAddress", _emailAddress, value, Nothing)
        End Set
    End Property

End Class

<BsonIgnoreExtraElements>
Public Class EMailModel : Inherits ModelBase
    Private _DisplayName As String
    Private _MailAddress As String

    Public Property DisplayName As String
        Get
            Return _DisplayName
        End Get
        Set(value As String)
            SetProperty("DisplayName", _DisplayName, value, Nothing)
        End Set
    End Property
    Public Property MailAddress As String
        Get
            Return _MailAddress
        End Get
        Set(value As String)
            SetProperty("MailAddress", _MailAddress, value, Nothing)
        End Set
    End Property
End Class




参照サイト:
http://ivis-mynikki.blogspot.jp/2013/01/net-or.htmlhttp://ivis-mynikki.blogspot.jp/2014/03/blog-post.html
http://blog.micic.ch/net/easy-mvvm-example-with-inotifypropertychanged-and-inotifydataerrorinfo
 
 
 

はじめてのVB.NetでMongoDBの使い方

前から使いたかったMongoDB。
サービス登録ってどうやるの?.netだと使い勝手悪いんじゃ?など
色々と調べる事があるから使わないでMSSQLやテキストファイルの
CSV,JSONで十分じゃんって自分に言いきかせて触らずにいたのだが
こういったデータは複数のプログラムでリアルタイムに更新したかったりするので
今回本気に使ってみることにした。

まず覚えなくてはいけない事。
 1.MongoDBをダウンロードすること。
 2.DB、LOGフォルダを用意すること。
 3.サービス登録しておくこと。
 4.VisualStudioのNugetで「Official MongoDB C# Driver」をインストールすること。(Apache2.0ライセンス)
 5.ライブラリの使い方、DBの接続方法、データの追加、削除、更新を覚えること。

1,2,3まではやる気さえあればできそうで
4,5になると開発環境を起動したりコードを書いたりしなきゃならないので遠ざかりたくなるけど、使いたいという思いでどうにか乗り切るしかなさそう。
なるべく手順をまとめると、

Step1. MongoDBのダウンロード、DB、LOGフォルダの用意、サービス登録を行う。

  http://www.mongodb.org/

上記公式サイトからMongoDB本体をダウンロードしてきて任意の場所に展開する。
そしてダウンロードしながらどこかにDB、LOGフォルダを用意してから
管理者権限でコマンドプロンプトを開く。

今回は
 d:\tool\mongodb       MongoDB展開フォルダ
 d:\tool\mongodb\bin\log  Logフォルダ
 d:\tool\mongodb\bin\db  DBフォルダ

というようにした場合、下記コマンドを入力する。

>cd \tool\mongodb\bin
>mongod --install --dbpath .\db --logpath .\log\log.txt

サービス解除する時は --install を --remove にすればOK。

Step2.使い方を覚える

 まずは、VisualStudioを起動してソリューションを1つ作ってから
Nugetで「Official MongoDB C# Driver」をインストール。

あとはコーディング。


Imports MongoDB.Driver
Imports MongoDB.Driver.Builders
Imports MongoDB.Driver.Linq
Imports MongoDB.Bson
Imports MongoDB.Bson.Serialization.Attributes


Class MainWindow
    Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
        'MongoDBサーバーに接続して、操作するコレクションを選択。
        Dim mongo_drv = New MongoClient()
        Dim db = mongo_drv.GetServer().GetDatabase("TestDB")
        Dim col = db.GetCollection(Of AddressModel)("AddressCollection")

        'サンプルデータの追加
        col.Insert(New AddressModel() With {
            .Name = "test1",
            .Birthday = Date.Parse("1980/12/5"),
            .EMailAddress = New List(Of EMailModel)() From {
                New EMailModel With {.DisplayName = "test1", .MailAddress = "mail1@test.com"},
                New EMailModel With {.DisplayName = "test2", .MailAddress = "mail2@test.com"}
            }
        })
        col.Insert(New AddressModel() With {
            .Name = "test1",
            .Birthday = Date.Parse("1981/12/5"),
            .EMailAddress = New List(Of EMailModel)() From {
                New EMailModel With {.DisplayName = "test3", .MailAddress = "mail3@test.com"},
                New EMailModel With {.DisplayName = "test4", .MailAddress = "mail4@test.com"}
            }
        })

        'MondoDBサーバー切断
        mongo_drv.GetServer().Disconnect()
    End Sub

    Private Sub Button_Click_1(sender As Object, e As RoutedEventArgs)
        'MongoDBサーバーに接続して、操作するコレクションを選択。
        Dim mongo_drv = New MongoClient()
        Dim db = mongo_drv.GetServer().GetDatabase("TestDB")
        Dim col = db.GetCollection(Of AddressModel)("AddressCollection")

        'LINQで検索
        For Each model In (From t In col.AsQueryable() Where t.Name.StartsWith("test") Select t)
            Console.WriteLine("--------")
            Console.WriteLine(model.Name)
            Console.WriteLine(model.Birthday)
            For Each emodel In model.EMailAddress
                Console.WriteLine(emodel.DisplayName)
                Console.WriteLine(emodel.MailAddress)
            Next
            Console.WriteLine("--------")
        Next

        'MondoDBサーバー切断
        mongo_drv.GetServer().Disconnect()
    End Sub

    Private Sub Button_Click_2(sender As Object, e As RoutedEventArgs)
        'MongoDBサーバーに接続して、操作するコレクションを選択。
        Dim mongo_drv = New MongoClient()
        Dim db = mongo_drv.GetServer().GetDatabase("TestDB")
        Dim col = db.GetCollection(Of AddressModel)("AddressCollection")

        'LINQで検索、該当項目の日付を更新する(GMTがまちがってそう。。)
        For Each model In (From t In col.AsQueryable() Where t.Name.StartsWith("test") Select t)
            model.Birthday = Date.Now
            col.Save(model)
        Next

        mongo_drv.GetServer().Disconnect()
    End Sub

    Private Sub Button_Click_3(sender As Object, e As RoutedEventArgs)
        'MongoDBサーバーに接続して、操作するコレクションを選択。
        Dim mongo_drv = New MongoClient()
        Dim db = mongo_drv.GetServer().GetDatabase("TestDB")
        Dim col = db.GetCollection(Of AddressModel)("AddressCollection")

        'LINQで検索、該当項目の削除
        For Each model In (From t In col.AsQueryable() Where t.Name.StartsWith("test") Select t)
            col.Remove(Query.EQ("_id", model._id))
        Next

        mongo_drv.GetServer().Disconnect()
    End Sub
End Class


Public Class AddressModel
    
    Public Property _id As BsonObjectId
    Public Property Name As String
    Public Property Birthday As Date
    Public Property EMailAddress As List(Of EMailModel)

End Class


Public Class EMailModel
    Public Property DisplayName As String
    Public Property MailAddress As String
End Class



日付の扱いがちょっとGMTがらみの調整が必要かもしれないけど、ひとまず使えるようになったのでここまで。


2014年3月18日火曜日

[python]pip 1.5.4にアップデートしたらutf-8エラーが出た。 for windows

バグFixついでにpipを1.0.1から1.5.4へアップデートしたら無慈悲なエラーが吐き出された。
しかも赤い文字でエラーまで出る始末。。


 Traceback (most recent call last):
  File "C:\traclight\python\Scripts\pip-script.py", line 8, in
    load_entry_point('pip==1.5.4', 'console_scripts', 'pip')()
  File "C:\TracLight\python\lib\site-packages\pip-1.5.4-py2.6.egg\pip\__init__.p
y", line 185, in main
    return command.main(cmd_args)
  File "C:\TracLight\python\lib\site-packages\pip-1.5.4-py2.6.egg\pip\basecomman
d.py", line 161, in main
    text = '\n'.join(complete_log)
  File "C:\TracLight\python\Lib\encodings\utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x82 in position 34: invalid
start byte



pythonを扱う上では文字コードは毎回の事エラーが出るし、そこまで驚かないけど、
英語圏の人は文字コード関係なしなんだろうなーと思ってしまう。


治す取っ掛かりは、エラーの出た個所を追っていくと文字データに2バイト文字が含まっている変数が見つかるので、 Windows環境であればCP932でdecodeしてあげれば良い。

そんなわけで探していくと、

download.py 472行目付近
temp_dirにアカウント名(2バイト文字)が入っていたので、さっそくdecodeデコード。


def unpack_http_url(link, location, download_cache, download_dir=None,
                    session=None):
    if session is None:
        session = PipSession()

    temp_dir = tempfile.mkdtemp('-unpack', 'pip-')
    try:
        temp_dir = temp_dir.decode('cp932')
    except:
        pass


asciiやutf-8はそのままでいいし、eucは使わないし、
そんなやっつけ仕事で動く程度の修正して解決。

Androider