Git Product home page Git Product logo

livet's Introduction

Livet

English version is here.

Livet とは

Livet(リベット) は WPF のための MVVM(Model/View/ViewModel) パターン用インフラストラクチャです。 .NET Framework 4.5.2 以上及び .NET Core 3.1, .NET 6 で動作し zlib/libpng ライセンスで提供しています。 zlib/libpng ライセンスでは、ライブラリとしての利用にとどめるのであれば再配布時にも著作権表示などの義務はありません。

しかし、ソースコードを改変しての再配布にはその旨の明示が義務付けられます。

GitHub スポンサー

応援してもらえるとモチベーションが上がります。

Name GitHub Sponsors
runceel https://github.com/sponsors/runceel

導入

Livet は Visual Studio 2022 の拡張機能を使用することでプロジェクトテンプレートやアイテムテンプレートやコードスニペットが追加され生産性が一番高い状態で開発が出来るように設計されています。 拡張機能は Visual Studio の拡張機能と更新プログラム でオンラインカテゴリーで Livet と検索するインストールすることが出来ます。

また、ライブラリは以下 NuGet に公開しています。

  • LivetCask
    • 従来通りの Livet の機能セット
  • LivetCask2
    • Livet のコレクションを StatefulModel ベースのコレクションに置き換えたパッケージ
  • LivetExtensions

さらに、Livet のフル機能は利用しないが一部の機能を利用したい場合に対応するため、機能単位のパッケージを提供しています。上記の LivetCask と LivetCask2 は、下記のパッケージを束ねたものになります。

Visual Studio との親和性

Livet は極力 Visual Studio の機能を活かす作りにしています。

Visual Studio 対応

Livet ではプロジェクトテンプレート、アイテムテンプレート、コードスニペットを提供しています。

プロジェクトテンプレート

アイテムテンプレート

コードスニペットは以下のものを提供しています。

  • lvcom : ViewModelCommand
  • lvcomn : ViewModelCommand(Non CanExecute)
  • llcom : ListenerCommand(with a parameter)
  • llcomn : ListenerCommand(with a parameter, Non CanExecute)
  • lprop : Notification property
  • lsprop : Notification property(Short version)

View サポート

Livet では View レイヤーでデータバインディングが出来る箇所を可能な限り増やすように設計されています。

バインドできないプロパティをバインド可能にするビヘイビア

WPF では依存関係プロパティに対するデータバインディングがサポートされていますが、コントロールの全てのプロパティが依存関係プロパティとして定義されているわけではありません。 そのため、データバインディングで実装したくても素直に出来ないためコードビハインドに手動で View と ViewModel のデータ同期のコードが必要になるケースがあります。

Livet では、すべてのコントロールの全ての(System.Windows で始まる型のプロパティは除く)依存関係プロパティではないプロパティの端方向のバインドを可能にするビヘイビアとアクションを提供しています。

<Button Height="50" Content="マウスを乗せてください">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseEnter">
            <!--  IsMouseOver は本来バインドできないプロパティ  -->
            <l:ButtonSetStateToSourceAction Source="{Binding ButtonMouseOver, Mode=TwoWay}" Property="IsMouseOver" />
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseLeave">
            <!--  IsMouseOver は本来バインドできないプロパティ  -->
            <l:ButtonSetStateToSourceAction Source="{Binding ButtonMouseOver, Mode=TwoWay}" Property="IsMouseOver" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>
<WebBrowser Grid.Row="1" Grid.ColumnSpan="2">
    <i:Interaction.Behaviors>
        <!--  Source は本来バインディング出来ないプロパティ  -->
        <l:WebBrowserSetStateToControlBehavior Source="{Binding Url}" Property="Source" />
    </i:Interaction.Behaviors>
</WebBrowser>

また、TextBox 用に本来バインディング出来ない SelectedText、SelectionLength、SelectionStart プロパティを双方向バインド可能にするビヘイビアを提供しています。

<TextBox>
    <i:Interaction.Behaviors>
        <l:TextBoxBindingSupportBehavior
            SelectedText="{Binding SelectedText}"
            SelectionLength="{Binding SelectionLength}"
            SelectionStart="{Binding SelectionStart}" />
    </i:Interaction.Behaviors>
</TextBox>

TextBox と同様に PasswordBox 用に本来バインディング出来ない Password プロパティを双方向バインド可能にするビヘイビアを提供しています。

<PasswordBox>
    <i:Interaction.Behaviors>
        <l:PasswordBoxBindingSupportBehavior Password="{Binding Password}" />
    </i:Interaction.Behaviors>
</PasswordBox>

View のイベントから ViewModel のメソッドを呼ぶアクション

Livet ではコマンドをサポートしていないイベントに対してメソッド呼び出しをする LivetCallMethodAction を提供します。 標準の CallMethodAction よりもパフォーマンスが良く、機能面でも MethodParameter プロパティを使用してメソッドに 1 つの引数を渡すことが出来ます。

<TextBox x:Name="textBox">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="TextChanged">
            <l:LivetCallMethodAction
                MethodName="TextChanged"
                MethodParameter="{Binding Text, ElementName=textBox}"
                MethodTarget="{Binding}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TextBox>

Messenger

Livet.Behaviors.Messaging 名前空間には Livet の Messenger などから送られたメッセージを受け取ることの出来る Action が用意されています。この Action は Messenger からメッセージを受け取るだけではなく、EventTrigger などから Action を起動することが出来るようになっています。

これにより、クリックイベントなどをきっかけにして別ウィンドウを表示したり、確認ダイアログを出したりといったことが ViewModel を経由せずに行えるためシンプルになります。

参考
ボタンクリックをきっかけに確認ダイアログを出して結果を元に ViewModel で処理しようとすると、一般的な MVVM をサポートするライブラリでは View でのイベントを ViewModel のコマンドで受けて、コマンドの処理の中でメッセンジャーを呼び出して View にメッセージを送って、View 側で処理を行い結果をコールバックで ViewModel に返すといった処理になります。

一般的な MVVM ライブラリの処理の流れ: View -> ViewModel -> View -> ViewModel
Livet の処理の流れ: View -> ViewModel

また、戻り値のあるメッセージ(確認ダイアログの表示メッセージなど)では、その戻り値を引数に渡して ViewModel のメソッドやコマンドの呼び出しにも対応しています。

ViewModel 起点の場合は、まず View に InteractionMessageTrigger を設定して Action を指定します。

<l:InteractionMessageTrigger MessageKey="MessageKey_Confirm" Messenger="{Binding Messenger}">
    <l:ConfirmationDialogInteractionMessageAction />
</l:InteractionMessageTrigger>

そして、ViewModel 側で Messenger を使用してメッセージを送ります。

public async void ConfirmFromViewModel()
{
    var message = new ConfirmationMessage("これはテスト用メッセージです。", "テスト", "MessageKey_Confirm")
    {
        Button = MessageBoxButton.OKCancel,
    };
    await Messenger.RaiseAsync(message);
    OutputMessage = $"{DateTime.Now}: ConfirmFromViewModel: {message.Response ?? false}";
}

MessageKey_Confirm というメッセージキーを元にして、どの InteractionMessageTrigger が反応するかが決まります。

View 起点の場合は Action に対して DirectInteractionMessage を指定して、DirectInteractionMessage の中に実際のメッセージを定義します。例えば、ボタンをクリックしたら確認ダイアログを出す場合は以下のようになります。

<Button Content="ConfirmFromView">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <l:ConfirmationDialogInteractionMessageAction>
                <l:DirectInteractionMessage CallbackMethodName="ConfirmFromView" CallbackMethodTarget="{Binding}">
                    <l:ConfirmationMessage Caption="テスト" Text="これはテスト用メッセージです。" />
                </l:DirectInteractionMessage>
            </l:ConfirmationDialogInteractionMessageAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

上記の例では、Action の実行後に ViewModel の ConfirmFromView メソッドを呼ぶように指定しています。ConfirmFromView メソッドではメッセージを引数に受け取り処理を行えます。

public void ConfirmFromView(ConfirmationMessage message)
{
    OutputMessage = $"{DateTime.Now}: ConfirmFromView: {message.Response ?? false}";
}

Livet に標準で定義されているメッセージとアクションの組み合わせには、確認・情報ダイアログ・ファイルダイアログ・画面遷移・フォルダーダイアログ(Windows API Code Packを参照しているため Livet.Extensions という別アセンブリに定義)などがあります。

Livet.Extensions のフォルダー選択メッセージとアクションの使用例。

<Button Content="Folder">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <l:FolderBrowserDialogInteractionMessageAction>
                <l:DirectInteractionMessage CallbackMethodName="FolderSelected" CallbackMethodTarget="{Binding}">
                    <l:FolderSelectionMessage Description="フォルダーの選択" DialogPreference="None" />
                </l:DirectInteractionMessage>
            </l:FolderBrowserDialogInteractionMessageAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

汎用 EnumToBooleanConverter

Livet では System.Windows 名前空間以下の全ての Enum 型を boolean と相互変換する IValueConverter を用意しています。

<Window.WindowState>
    <Binding ElementName="checkBox" Path="IsChecked">
        <Binding.Converter>
            <l:WindowStateAndBooleanConverter
                ConvertBackDefaultBooleanValue="False"
                ConvertBackWhenNormal="True"
                ConvertWhenFalse="Maximized"
                ConvertWhenTrue="Normal" />
        </Binding.Converter>
    </Binding>
</Window.WindowState>

その他の View 機能

Microsoft.Xaml.Behaviors.Wpf の DataTrigger は初期値に対応していません。その対処として初期値に対応する LivetDataTrigger、フォーカスを制御する SetFocusAction 、Window のクローズキャンセルや、クローズキャンセル可否判断を ViewModel に委譲する事が出来る WindowCloseCancelBehavior、RoutedEventTrigger、DataContext が IDisposable であった場合 DataContext を Dispose する DataContextDisposeAction などを用意してあります。

ViewModel サポート

Livet は Presentation Domain Separation(PDS) に沿って開発されることを前提としています。その前提の上では ViewModel にはあまりコードが書かれないという考えの上で ViewModel サポートの機能を提供しています。

ViewModel の Messenger プロパティと CompositeDisposable プロパティ

Messenger は前述の View サポートの Messenger で解説したメッセージを受け取るアクションに ViewModel からメッセージを伝えるために用意されています。CompositeDisposable プロパティは IEnumerable インターフェースを実装していて、ViewModel が Dispose される際に同時に破棄したいリソースを格納するために使用されます。

Livet の ViewModel 機能を使用する場合は、ViewModel と一緒に破棄したいリソースは基本的に ViewModel の CompositeDisposable プロパティに追加する事になります。

CompositeDisposable.Add(someResource);

DispatcherCollectionとReadOnlyDispatcherCollection

DispatcherCollection は、既存の変更通知コレクションをコンストラクタの引数にとり、その変更通知を指定された Dispatcher 上で行うコレクションです。ReadOnlyDispatcherCollectionは、DispatcherCollection をコンストラクタの引数にとる読み取り専用ラッパーとなります。

ViewModelHelper.CreateReadOnlyDispatcherCollection<TModel,TViewModel>

CreateReadOnlyDispatcherCollection を使用すると、Model の変更通知コレクションを指定し、その Model のコレクションの変更と連動する ReadOnlyDispatcherCollection を生成できます。Func<TModel,TViewModel> を指定して TMode l型と TViewModel 型の相互変換を行います。

var source = new ObservableCollection<int> { 1, 2, 3 };
var result = ViewModelHelper.CreateReadOnlyDispatcherCollection(
    source,
    x => new TestViewModel(x * x),
    DispatcherHelper.UIDispatcher);

CreateReadOnlyDispatcherCollection を使用して生成された TViewModel の要素が IDisposable であった場合、source コレクションから Remove された場合など要素削除時には Dispose が呼ばれます。 生成されたコレクションの Dispose を呼ぶことで、ソースコレクションとの連動は解除され、TViewModel が IDisposable であった場合は生成された TViewModel 型に対して Dipose が呼ばれます。

PropertyChangedEventListener/CollectionChangedEventListener

ViewModel では Model のイベントを監視するケースが多くあります。C# でイベントハンドラの監視と解除をラムダ式を使って行うと解除が出来ないという問題があります。

// 購読解除できない
var model = new Model();
model.PropertyChanged += (s, e) =>
{
    // do something
};

Livet の PropertyChangedEventListener を使用すると、ラムダ式を使用したプロパティ変更のイベントのハンドリングが出来て購読解除を行うことも出来ます。

var model = new Model();
var listener = new PropertyChangedEventListener(model)
{
    // コレクション初期化子で指定可能
    {
        // プロパティ名指定でイベントハンドラーを設定
        nameof(Model.Input1),
        (s, e) =>
        {
            // do something
        }
    },
    {
        // プロパティ名を式木で設定
        () => model.Input2,
        (s, e) =>
        {
            // do something
        }
    },
    // プロパティ名を指定せずに PropertyChanged イベントに対する処理も設定可能
    (s, e) =>
    {
        // do something
    }
};

// 個別登録も可能
listener.RegisterHandler(nameof(Model.Input1), (s, e) => { });
listener.RegisterHandler(() => model.Input2, (s, e) => { });
listener.RegisterHandler((s, e) => { });

// 購読解除(ViewModel の破棄時に解除する場合は前述の CompositeDisposable を使用
listener.Dispose();

CollectionChangedEventListener はそのコレクション変更通知版です。PropertyChangedEventListener ほど多機能ではありませんが似た様な事が可能です。 また、汎用 EventListener として EventListener を用意してあります。

var model = new Model();
var handler = new EventListener<EventListener<SampleEventArgs>>(
    h => model.SampleEvent += h,
    h => model.SampleEvent -= h,
    (s, e) =>
    {
        // do something
    });

// 購読解除
handler.Dispose();

Model サポート

Model は MVVM の関心領域ではないため、Livet でも特に手厚いサポートはありません。 しかし最低限の機能として、変更通知イベント基底クラスとなる NotificationObject と、スレッドセーフな変更通知コレクションである ObservableSynchronizedCollection を用意してあります。 ObservableSyncronizedCollection は Monitor ベースのスレッドセーフなコレクションではなく、ReaderWriterLockSlim によるスレッドセーフなコレクションであり、読み書きが等しく複数スレッドから煩雑に行われるシナリオにおいて、パフォーマンスと変更通知のタイミングの偏りのバランスが良い様に設計されています。 前述した ViewModel の DispatcherCollection はただのラッパーであるため、Model では通常の ObservableCollection と ObservableSyncronizedCollection を用途に応じて使い分け、それを ViewModel では同様に扱う事ができます。

NotificationObject にはプロパティの定義を簡略化するための以下のメソッドが定義されています。また、NotificationObject は ViewModel クラスにも継承されているため、ここで説明する内容は ViewModel でも使用できます。

RaisePropertyChanged メソッド

プロパティ名指定で PropertyChanged イベントを発行します。

private string _Name;
public string Name
{
    get => _Name;
    set
    {
        _Name = value;
        RaisePropertyChanged(); // CallerMemberName により自動でプロパティ名が設定されます
    }
}

明示的に指定することも出来ます。

RaisePropertyChanged(nameof(Name));

RaisePropertyChangedIfSet メソッド

フィールドの更新と PropertyChanged イベントの発行を行います。このメソッドを使うとプロパティの定義は以下のようになります。このプロパティの定義は、コードスニペットの lsprop で生成できます。

private string _Name;
public string Name
{
    get => _Name;
    set => RaisePropertyChangedIfSet(ref _Name, value);
}

RaisePropertyChangedIfSet メソッドでは、第三引数に関連するプロパティ名を渡すことで、そのプロパティに対しても PropertyChanged イベントを発行出来ます。

private string _FirstName;
public string FirstName
{
    get => _FirstName;
    // FirstName の他に FullName の PropertyChanged イベントも発行する
    set => RaisePropertyChangedIfSet(ref _FirstName, value, nameof(FullName));
}

private string _LastName;
public string LastName
{
    get => _LastName;
    // FirstName の他に FullName の PropertyChanged イベントも発行する
    set => RaisePropertyChangedIfSet(ref _LastName, value, nameof(FullName));
}

public string FullName => $"{FirstName} {LastName}";

関連するプロパティが複数ある場合は配列で渡します。

public string _Hoge;
public string Hoge
{
    get => _Hoge;
    set => RaisePropertyChangedIfSet(ref _Hoge, value, new[]
    {
        nameof(Foo),
        nameof(Bar),
        nameof(Baz),
    });
}

他の MVVM フレームワークとの併用

Livet 3.0.3 以降 Livet のパッケージは機能単位に分割されました。 他の MVVM フレームワークを利用しているケースでも Livet のビヘイビアーが使いたい、コンバーターが使いたいといった場合は該当パッケージのみを NuGet から導入していただくことで、その機能だけを利用することが可能です。

LivetCask.Mvvm

Livet の NotificationObject や ViewModel 及びコマンド系クラスが含まれます。

LivetCask.Collections

Livet のコレクション機能が含まれています。

LivetCask.StatefulModel

LivetCask.Collections を置き換えるためのコレクションライブラリです。 詳細については StatefulModel を参照してください。

LivetCask.Behaviors

Livet で提供している LivetInvokeMethodAction などのビヘイビアーや、そのほかのバインドできないプロパティへのバインドをサポートするビヘイビアーが含まれます。

LivetCask.Converters

WPF 関連の enum と bool 間の変換をサポートするコンバーターが含まれます。

LivetCask.EventListeners

PropertyChangedEventListener/CollectionChangedEventListener/EventListener が含まれます。

LivetCask.Messaging

Livet の Messenger クラスや、関連する様々な Message クラスと、それを処理するための Behavior などが含まれます。

特に View 起点のメッセージは、他の MVVM フレームワークでは、あまり見ない機能になります。

LivetExtensions

フォルダーダイアログが含まれます。また Windows API Code Pack のアセンブリも同梱されています。

オリジナルドキュメント

本ドキュメントは下記のオリジナルドキュメントを元に加筆修正を行ったものです。

Livet Project Home

今後のサポート方針

基本的に WPF がサポートされているプラットフォームに対する対応を行います。 新機能の積極的な追加などは行う予定はありません。

新機能が必要な場合は PullRequest をお願いします。機能追加の PullRequest には単体テストをつけて動作確認が出来る状態でお願いします。

Issue にはなるべく目を通すつもりですが、メール通知などを見落とすことがあります。 ノーレスポンスにするつもりはないので、もし一週間程度反応が無い場合は Twitter の @okazuki 宛にメンションください。気付くまで定期的に送ることをお勧めします。

Authors/Mentainers

  • Original author: 尾上 雅則 (Masanori Onoue) Twitter ID: ugaya40
  • Mentainer: 大田 一希(Kazuki Ota) Twitter ID: okazuki

livet's People

Contributors

edy4c7 avatar fardoc avatar gab-km avatar grabacr07 avatar karno avatar ledsun avatar natsuki14 avatar onouen avatar posaunehm avatar runceel avatar soi013 avatar terasato avatar ugaya40 avatar veigr avatar yoshihiroito avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

livet's Issues

WindowActionMessageのバグ

WindowActionMessageのCreateInstanceCoreメソッドのオーバーライドにバグがあります。
return new WindowActionMessage(MessageKey);
となっていますが、正しくは
return new WindowActionMessage(Action, MessageKey);
のはずです。
こうしないと、ViewModelからWindowActionMessageをRaiseしたときに、渡したActionがウィンドウに正しく作用しません。

ViewModelA->ViewModelBのメッセージ通知

こんにちは、

また、困ったことがあるので、改めてご連絡いたします。

ViewModelAからViewModelBへメッセージを渡したいですが、Livetはこの行動をサポートしますか。
今、MVVM Light Messengerツールで渡されます。こういう感じです。
image
http://dotnetpattern.com/mvvm-light-messenger

例えば:ViewModelAからViewModelBへメッセージを渡す。

ViewModelAのコード

MessengerInstance.Send("ToViewModelC"); //「ToViewModelC」のメッセージを渡す。

ViewModelBのコード

MessengerInstance.Register<string>(this, message=> 
{
    if (message == "ToViewModelC")
    {
        // 習得されたmessageで操作する
    }
});

ですが、Livetに変更してみたいです。このような行動はサポートがあるのですか。

ご指導のほどよろしくお願いいたします。

Latest version Livet throws error on WPF.

@runceel @karno @Gab-km @YoshihiroIto @ledsun
Hi,
My WPF (.Net framework) 4.5.2 application was using older version of LivetCask(2.2.0), that time it was working fine.
Now i updated the LivetCask into 3.2.1. When updating the latest package using Nuget, some other LivetCask components also installed.

The blow code shows error :
the value of type can not be added to a collection or dictionary of type triggeractioncollection for event trigger.

<i:Interaction.Triggers> <i:EventTrigger EventName="ContentRendered"> <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/> </i:EventTrigger> <i:EventTrigger EventName="Closed"> <l:DataContextDisposeAction/> </i:EventTrigger> <l:InteractionMessageTrigger Messenger="{Binding Path=Messenger}" MessageKey="ok"> <l:WindowInteractionMessageAction/> </l:InteractionMessageTrigger> <l:InteractionMessageTrigger Messenger="{Binding Path=Messenger}" MessageKey="cancel"> <l:WindowInteractionMessageAction/> </l:InteractionMessageTrigger> </i:Interaction.Triggers>

Please help me on this.

Nuget ライブラリの更新

こんにちは。Nugetライブラリの更新方法が分かりませんので,お願いできませんでしょうか。お手数おかけします。

.NET 6 で FolderBrowserDialog を開こうとするとエラー

Livet 4.0.1 + .NET 6 + VS 2022 で FolderBrowserDialog を開こうとするとエラーが発生します。

Something errors were occurred.
Could not load file or assembly 'Microsoft.WindowsAPICodePack.Shell, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. 指定されたファイルが見つかりません。
error

ターゲットフレームワークを .NET 5 にするとエラーは発生しません。

テストプログラム:
https://github.com/shinta0806/TestFolderBrowserDialog

CursorTypeAndBooleanConverter is not working

CursorTypeAndBooleanConverter is not working.
Because, it should convert to string instead of CursorType.

private CursorType _convertWhenTrue;
        public CursorType ConvertWhenTrue
        {
            get { return _convertWhenTrue; }
            set
            {
                _convertWhenTrue = value;
                _isConvertWhenTrueSet = true;
            }
        }

👇

private string _convertWhenTrue;
        public string ConvertWhenTrue
        {
            get { return _convertWhenTrue; }
            set
            {
                _convertWhenTrue = value;
                _isConvertWhenTrueSet = true;
            }
        }

https://www.c-sharpcorner.com/forums/how-to-bind-wait-cursor-using-mvvm-in-wpf

I can write fix code and PR by hand.
But, this class is generated.
Sorry, I don't know T4 template.

README.mdの画像リンク切れ

権限付与ありがとうございました。

README.mdの「Working with Visual Studio and Livet」のセクションで,スクリーンショットのリンク切れです。過去のコミットから復活させれば良いのでしょうか?

Working for .NET 6 and Visual Studio 2022

Livet is for .NET Core 3.x now. And The Livet Extension is for Visual Studio 2019 now.

  • Migrate to .NET 6
  • Migrate to .NET Framework 4.6
  • Migrate to Visual Studio 2022 and 2019

FolderSelectionMessage cause InvalidOperationException in ver 3.2.3.2

LivetCask 3.2.3.2
LivetExtension 3.2.3.2

Both .NET3.1 and .NET Framework4.6.2

Exception

System.InvalidOperationException
  HResult=0x80131509
  Message=オブジェクト 'Livet.Messaging.IO.FolderSelectionMessage' は読み取り専用状態であるため、そのプロパティを設定できません。
  Source=WindowsBase
  スタック トレース:
   at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
   at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
   at Livet.Messaging.IO.FolderSelectionMessage.set_SelectedPaths(String[] value)
   at Livet.Behaviors.Messaging.IO.FolderBrowserDialogInteractionMessageAction.InvokeAction(InteractionMessage m)
   at Livet.Behaviors.Messaging.InteractionMessageAction`1.Invoke(Object parameter)
   at Microsoft.Xaml.Behaviors.TriggerAction.CallInvoke(Object parameter)
   at Microsoft.Xaml.Behaviors.TriggerBase.InvokeActions(Object parameter)
   at Livet.Behaviors.Messaging.InteractionMessageTrigger.<>c__DisplayClass21_0.<MessageReceived>b__1()
   at Livet.Behaviors.Messaging.InteractionMessageTrigger.DoActionOnDispatcher(Action action)
   at Livet.Behaviors.Messaging.InteractionMessageTrigger.MessageReceived(Object sender, InteractionMessageRaisedEventArgs e)
   at Livet.EventListeners.WeakEvents.LivetWeakEventListener`2.ReceiveEvent(WeakReference`1 listenerWeakReference, Object sender, TEventArgs args)
   at Livet.EventListeners.WeakEvents.LivetWeakEventListener`2.<>c__DisplayClass9_0.<GetStaticHandler>b__0(Object sender, TEventArgs e)
   at Livet.Messaging.InteractionMessenger.Raise(InteractionMessage message)
   at SampleAppNetCore.MainWindowViewModel.OpenFolderBrowser() in ...

Reproduction Code

MainWindow.xaml

<Window x:Class="SampleAppNetCore.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:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:SampleAppNetCore"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <i:Interaction.Triggers>
        <l:InteractionMessageTrigger MessageKey="show_selectfolderdialog" Messenger="{Binding Messenger}">
            <l:FolderBrowserDialogInteractionMessageAction/>
        </l:InteractionMessageTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="File">
                <MenuItem Header="Open FolderBrowser" Command="{Binding OpenFolderBrowserCommand}"/>
            </MenuItem>
        </Menu>
    </Grid>
</Window>

MainWindowViewModel.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Livet;
using Livet.Commands;
using Livet.Messaging;
using Livet.Messaging.IO;

namespace SampleAppNetCore
{
	class MainWindowViewModel : ViewModel
	{
		#region コマンド: OpenFolderBrowserCommand
		private ViewModelCommand _OpenFolderBrowserCommand;
		public ViewModelCommand OpenFolderBrowserCommand => _OpenFolderBrowserCommand ?? (_OpenFolderBrowserCommand = new ViewModelCommand(OpenFolderBrowser, CanOpenFolderBrowser));
		public bool CanOpenFolderBrowser() => true;
		public void OpenFolderBrowser()
		{
			if (!CanOpenFolderBrowser()) return;

			var message = new FolderSelectionMessage("show_selectfolderdialog") {
				Title = "Save image files",
			};

			Messenger.Raise(message);
			if (string.IsNullOrWhiteSpace(message.Response)) {
				return;
			}

			var folderPath = message.Response;
			Debug.WriteLine(folderPath);
		}
		#endregion
	}
}

Exception occurred here

    public class FolderBrowserDialogInteractionMessageAction : InteractionMessageAction<FrameworkElement>
    {
        protected override void InvokeAction(InteractionMessage m)
        {
            if (m is FolderSelectionMessage folderSelectionMessage)
            {
                var hostWindow = Window.GetWindow(AssociatedObject ?? throw new InvalidOperationException());
                if (hostWindow == null) return;

                using (var dialog = FolderSelectionDialogFactory.CreateDialog(folderSelectionMessage.DialogPreference))
                {
                    if (dialog == null) throw new InvalidOperationException();

                    dialog.Title = folderSelectionMessage.Title;
                    dialog.Description = folderSelectionMessage.Description;
                    dialog.SelectedPath = folderSelectionMessage.SelectedPath;
                    dialog.Multiselect = folderSelectionMessage.Multiselect;

                    if (dialog.ShowDialog(hostWindow).GetValueOrDefault())
                    {
                        // V3.2.3.2 code -> InvalidOperationException
--->                    folderSelectionMessage.SelectedPaths = dialog.SelectedPaths;
                        folderSelectionMessage.Response = folderSelectionMessage.SelectedPaths.FirstOrDefault();

                        // V2.2.0 code -> No exception
                        //folderSelectionMessage.Response = dialog.SelectedPath;
                    } else
                    {
                        folderSelectionMessage.Response = null;
                    }
                }
            }
        }
    }

#いつもLivetを愛用しています。runceelさんメンテナンスありがとうございます。

MethodBinderWithArgument のInvoke()で、引数を定義型のサブクラスにすると呼び出しに失敗する

概要

TestMethod(Super arg) なメソッドを、Super のサブクラスの型 Sub を引数に呼び出すと ArgumentException が発生します。

再現手順

以下のコードを実行。

using System;
using Livet.Behaviors;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            TestNormal();
            TestBinder();
        }

        private static void TestNormal()
        {
            // MethodBinderWithArgument を使わない通常の呼び出し
            Super calleeArg = null;
            var test = new Test(x => calleeArg = x);
            var callerArg = new Sub();
            test.TestMethod(callerArg);
            Console.WriteLine(callerArg == calleeArg);  // True
        }

        private static void TestBinder()
        {
            // MethodBinderWithArgument を使った呼び出し
            Super calleeArg = null;
            var test = new Test(x => calleeArg = x);
            var binder = new MethodBinderWithArgument();
            var callerArg = new Sub();
            binder.Invoke(test, "TestMethod", callerArg);    // ArgumentException となる
            Console.WriteLine(callerArg == calleeArg);
        }
    }

    class Test
    {
        private readonly Action action;

        public Test(Action action)
        {
            this.action = action;
        }

        public void TestMethod(Super arg) => this.action?.Invoke(arg);
    }
    class Super { }
    class Sub : Super { }
}

期待結果

TestBinder() の実行結果は TestNormal() と同じとなる。

実際の結果

binder.Invoke() にて ArgumentException が発生する。

原因

以下の部分で引数のキャスト可否判定に使用している IsAssignableFrom の左右が逆と考えられます。

https://github.com/ugaya40/Livet/blob/master/.NET4.0/Livet(.NET4.0)/Behaviors/MethodBinderWithArgument.cs#L76

新しい Behavior ライブラリへの更新

Visual Studio 2019 以降は WPF で Behavior を使う場合には基本的に以下のものを使うか System.Windows.Interactivity が使いたい場合は Visual Studio 2017 から入れることになる。

https://github.com/Microsoft/XamlBehaviorsWpf

そのため、長期的にメンテナンスをしていくことを考えると既存の System.Windows.Interactivity から、新しい XamlBehaviorsWpf で提供されている Behavior に更新する必要がある。

代替案などがあれば、ここに書き込んでください。

FolderSelectionMessage cause FileNotFoundException in ver 3.2.3.

Hi

I used FolderSelectionMessage to Folder select dialog.

Ver 3.2.3 cause.
Ver 3.2.2 dose not cause.

Reproduction code

MainWindow.xaml

<Window 
   x:Class="FolderSelectDialogLivetTest.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"
   xmlns:l="http://schemas.livet-mvvm.net/2011/wpf">
   <Grid>
      <Button Content="Folder Dialog">
         <behaviors:Interaction.Triggers>
            <behaviors:EventTrigger EventName="Click">
               <l:FolderBrowserDialogInteractionMessageAction>
                  <l:DirectInteractionMessage>
                     <l:FolderSelectionMessage />
                  </l:DirectInteractionMessage>
               </l:FolderBrowserDialogInteractionMessageAction>
            </behaviors:EventTrigger>
         </behaviors:Interaction.Triggers>
      </Button>
   </Grid>
</Window>

FolderSelectDialogLivetTest.csproj

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="LivetCask" Version="3.2.3" />
    <PackageReference Include="LivetExtensions" Version="3.2.3" />
  </ItemGroup>
</Project>

Error Message

System.IO.FileNotFoundException
  HResult=0x80070002
  Message=Could not load file or assembly 'Microsoft.WindowsAPICodePack.Shell, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. 指定されたファイルが見つかりません。
  Source=Livet.Extensions
  スタック トレース:
   at Livet.Dialogs.FolderSelectionDialogFactory.get_CanUseCommonItemDialog()
   at Livet.Dialogs.FolderSelectionDialogFactory.CreateDialog(FolderSelectionDialogPreference preference)
   at Livet.Behaviors.Messaging.IO.FolderBrowserDialogInteractionMessageAction.InvokeAction(InteractionMessage m)
   at Livet.Behaviors.Messaging.InteractionMessageAction`1.Invoke(Object parameter)
   at Microsoft.Xaml.Behaviors.TriggerBase.InvokeActions(Object parameter)
   at Microsoft.Xaml.Behaviors.EventTriggerBase.OnEvent(EventArgs eventArgs)
   at Microsoft.Xaml.Behaviors.EventTriggerBase.OnEventImpl(Object sender, EventArgs eventArgs)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
   at System.Windows.Controls.Primitives.ButtonBase.OnClick()
   at System.Windows.Controls.Button.OnClick()
   at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
   at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
   at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
   at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run()
   at FolderSelectDialogLivetTest.App.Main()

nugetで2.1.0をインストールするときのエラー

重大度レベル コード 説明 プロジェクト ファイル 行 抑制状態
エラー 参照を追加できませんでした。パッケージ 'LivetCask' はフレームワーク参照を 'Microsoft.Expression.Interactions' に追加しようとしましたが、GAC に見つかりませんでした。これは、パッケージのバグである可能性があります。パッケージの所有者にお問い合わせください。
参照を使用できません。

InformationMessageによるメッセージボックス表示時のMessageBoxImageの指定について

Livetを使わせて頂いています。

どこに質問を投げればいいのかわからなかったため、こちらに投稿させていただきました。

Messengerを使用し、InformationMessageをViewに渡すことでメッセージボックスを表示しているのですが、
このメッセージボックスにMessageBoxImageを指定する方法はないのでしょうか?

FolderSelectionMessageでの複数選択

FolderSelectionMessageにおいて、複数のファイルを選択することはできますか?
さらに言えば、フォルダとファイルを混ぜて複数選択することは可能ですか。

よろしくおねがいします。

DispatcherCollection.Dispatcherはnull許容ですか?

経緯:

  • LivetにはReSharperのExternal Annotations用のファイルが用意されていないことに気づきました。自分で作ってしまおうと考え,現在作成中です
  • null許容かどうかを調査してみたところ,nullチェックが抜けている部分を複数発見しましたので, terasato:AddParamChecks としてプルリクを出しました(まだまだ出てくると思います

調査で気づいたのですが,DispatcherCollection.Dispatcher

  • setterでnullチェックがありません(nullが代入されうる状態)
  • ライブラリ内でnullチェックなくメソッド呼び出しがされています(NullReferenceExceptionがスローされる可能性があります。例:Dispatcher.CheckAccess()

DispatcherCollection.Dispatchernull許容として設計されているのであれば,メソッド呼び出しにnullチェックを追加する必要があります。null非許容として設計されているのであれば(そう思ってライブラリを使っていました),setterにnullチェックを追加する必要があります。

類似の問題はまだまだ出てきそうな感じがしています。安定性向上のため,お付き合いよろしくお願いします。

DataGridHyperlinkColumnのEventTrigger

こんにちは、

少し、困っているところがあるので、ご質問いたします。

DataGridにあるDataGridHyperlinkColumnの中にイベントを設定してほしいですが、EventTriggerのコードはどのように書けばいいでしょうか。

Livetを利用しない場合、このように書きます。

<DataGrid ItemsSource="{Binding ProductModel.ProductList}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridHyperlinkColumn Header="Product" Binding="{Binding Path='ProductName'}">
            <DataGridHyperlinkColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <EventSetter Event="Hyperlink.Click" Handler="HyperlinkProduct_Click"/>
                </Style>
            </DataGridHyperlinkColumn.ElementStyle>
        </DataGridHyperlinkColumn>
    </DataGrid.Columns>
</DataGrid>

HyperlinkProduct_Clickはコードビハインドのメソッドです。DataGridにあるProduct行をクリックするとHyperlinkProduct_Clickを呼ぶということです。

でも、LivetでMVVMパターンで作成するので、コードビハインドを書かなくて、ViewModelにあるメソッドを呼ぶのは正しいですが、EventTriggerのコードはどのように書けばいいでしょうか。下記のコードで呼べませんでした。

<DataGrid ItemsSource="{Binding ProductModel.ProductList}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridHyperlinkColumn Header="Product" Binding="{Binding Path='ProductName'}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <l:LivetCallMethodAction
                        MethodName="HyperlinkProductAction" 
                        MethodTarget="{Binding}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </DataGridHyperlinkColumn>
    </DataGrid.Columns>
</DataGrid>

HyperlinkProductActionはViewModelにあるメソッドです。

ご指導のほどよろしくお願いいたします。

FolderSelectionMessage.Response の型

FolderBrowserDialog でキャンセルボタンが押された際、FolderSelectionMessage.Response は null になるようですので、できれば FolderSelectionMessage.Response の型は null 許容型にしていただけるとありがたいです。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.