8

私は WPF アプリケーションで View First MVVM を使用することを余儀なくされており、それをエレガントに動作させる方法を見つけるのに苦労しています。

問題の根本は、ネストされた にありますUserControls。MVVM アーキテクチャでは、それぞれのUserControlビュー モデルをそのDataContextに割り当てる必要があります。これにより、バインディング式がシンプルに保たれます。さらに、これは、WPF が を介して生成されたビューをインスタンス化する方法でもありますDataTemplate

ただしUserControl、親が独自のビューモデルにバインドする必要がある依存関係プロパティが子にある場合、子が独自のビューモデルに設定されているという事実はUserControlDataContext親 XAML ファイルの「暗黙的なパス」バインディングが代わりに子のビューモデルに解決されることを意味します。親の。

これを回避するにUserControlは、アプリケーション内のすべてのすべての親は、デフォルトですべてに対して明示的な名前付きバインディングを使用する必要があります (これは冗長で、醜く、エラーが発生しやすい)、または特定のコントロールがDataContext独自のビューモデルに設定されているかどうかを知る必要があります。適切なバインディング構文を使用します (これは同様にエラーが発生しやすく、基本的なカプセル化の重大な違反です)。

何日にもわたる調査の後、私はこの問題に対する半分まともな解決策に出くわしていません。私が遭遇した解決策に最も近いのは、ビューモデルを(最上位またはその他UserControl'sの) 内部要素に設定することです。それでも、それ自体のプロパティを独自のビューモデルにバインドしようとする問題に直面します! (バインディングは、ビューモデルが割り当てられた名前付き要素の前に宣言されるため、この場合は機能しません)。UserControlGridUserControlElementNameDataContext

他の多くの人がこの問題に遭遇しない理由は、この問題のないビューモデルの最初の MVVM を使用しているか、この問題を改善する依存性注入の実装と組み合わせてビューの最初の MVVM を使用しているためだと思います。

誰かがこれに対するきれいな解決策を持っていますか?

アップデート:

リクエストされたサンプルコード。

<!-- MainWindow.xaml -->
<Window x:Class="UiInteraction.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:UiInteraction"
        Title="MainWindow" Height="350" Width="525"
        x:Name="_this">

    <Window.DataContext>
        <local:MainWindowVm/>
    </Window.DataContext>

    <StackPanel>
        <local:UserControl6 Text="{Binding MainWindowVmString1}"/>  
    </StackPanel>

</Window>
namespace UiInteraction
{
    // MainWindow viewmodel.
    class MainWindowVm
    {
        public string MainWindowVmString1
        {
            get { return "MainWindowVm.String1"; }
        }
    }
}
<!-- UserControl6.xaml -->
<UserControl x:Class="UiInteraction.UserControl6" x:Name="_this"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:local="clr-namespace:UiInteraction">

    <UserControl.DataContext>
        <local:UserControl6Vm/>
    </UserControl.DataContext>

    <StackPanel>
        <!-- Is bound to this UserControl's own viewmodel. -->
        <TextBlock Text="{Binding UserControlVmString1}"/>

        <!-- Has its value set by the UserControl's parent via dependency property. -->
        <TextBlock Text="{Binding Text, ElementName=_this}"/>
    </StackPanel>

</UserControl>
namespace UiInteraction
{
    using System.Windows;
    using System.Windows.Controls;

    // UserControl code behind declares DependencyProperty for parent to bind to.
    public partial class UserControl6 : UserControl
    {
        public UserControl6()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text", typeof(string), typeof(UserControl6));

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
    }
}
namespace UiInteraction
{
    // UserControl's viewmodel.
    class UserControl6Vm
    {
        public string UserControlVmString1
        {
            get { return "UserControl6Vm.String1"; }
        }
    }
}

これにより、次の結果が得られます。

System.Windows.Data エラー: 40: BindingExpression パス エラー: 'MainWindowVmString1' プロパティが 'object' ''UserControl6Vm' (HashCode=44204140)' に見つかりません。BindingExpression:Path=MainWindowVmString1; DataItem='UserControl6Vm' (HashCode=44204140); ターゲット要素は 'UserControl6' (Name='_this'); ターゲット プロパティは 'Text' (タイプ 'String') です

MainWindow.xaml宣言<local:UserControl6 Text="{Binding MainWindowVmString1}"/>では で解決しようとしているMainWindowVmString1ためですUserControl6Vm

最初の とUserControl6.xamlの宣言をコメントアウトすると、コードは機能しますが、 には. 暗黙的なパス バインディングの代わりに を使用することもできますが、バインディング構文を使用するには、 がそのビューモデルをその に割り当てることを知る必要があります(カプセル化の失敗) か、どこでもバインディングを使用するポリシーを採用する必要があります。どちらも魅力的ではありません。DataContextTextBlockUserControlDataContextMainWIndow1ElementNameElementNameUserControlDataContextElementName

4

3 に答える 3

2

当面の解決策は、 a を使用して、親RelativeSourceの を​​探すように設定することです。DataContextUserControl

<UserControl>
    <UserControl.DataContext>
        <local:ParentViewModel />
    </UserControl.DataContext>
    <Grid>
        <local:ChildControl MyProperty="{Binding DataContext.PropertyInParentDataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
    </Grid>
</UserControl>

子ビューモデルを親ビューモデルのプロパティとして扱い、親から伝播することもできます。そうすれば、親ビューモデルは子を認識して、プロパティを更新できます。子"Parent"ビューモデルには、親への参照を保持するプロパティもあり、作成時に親 itelf によって注入され、親への直接アクセスを許可する場合があります。

public class ParentViewModel : INotifyPropertyChanged
{
    #region INotifyPropertyChanged values

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion

    private ChildViewModel childViewModel;
    public ChildViewModel ChildViewModel
    {
        get { return this.childViewModel; }
        set
        {
            if (this.childViewModel != value)
            {
                this.childViewModel = value;
        this.OnPropertyChanged("ChildViewModel");
            }
        }
    }       
}

<UserControl>
    <UserControl.DataContext>
        <local:ParentViewModel />
    </UserControl.DataContext>
    <Grid>
        <local:ChildControl DataContext="{Binding ChildViewModel}"
            MyProperty1="{Binding PropertyInTheChildControlledByParent}"                
            MyProperty2="{Binding Parent.PropertyWithDirectAccess}"/>
    </Grid>
</UserControl>

編集 別のアプローチでより複雑なのは、添付プロパティを使用して親DataContextを子が利用できるようにすることです。UserControl私はそれを完全には実装していませんが、機能を要求するための添付プロパティ ( のようなもの"HasAccessToParentDT") で構成されます。このDependencyPropertyChanged場合、 の Load とUnloadイベントをChildUserControl接続し、Parentプロパティ (コントロールが読み込まれている場合に利用可能)にアクセスし、これDataContextを 2 番目の添付プロパティ にバインドし"ParentDataContext"、xaml で使用できるようにします。

        <local:ChildControl BindingHelper.AccessParentDataContext="True"
            MyProperty="{Binding BindingHelper.ParentDataContext.TargetProperty}"   />
于 2013-01-21T17:01:19.753 に答える
0

最も明白な解決策は、RelativeSource を使用することです。バインディング自体はあまりきれいに見えませんが、実際には非常によく見られます。私はそれを避けません-これがまさにそれがそこにある理由です。

使用できる別のアプローチは、それを持つことが論理的であれば、親ビューモデルへの参照です。ナビゲーション ポイントのリストとそのグラフィカルな「マップ」を並べて表示する FlightPlan ビューがあるように。ポイントのリストは、別のビューモデルを持つ別のビューです。

public class PlanPointsPartViewModel : BindableBase
{

    //[...]

    private FlightPlanViewModel _parentFlightPlan;
    public FlightPlanViewModel ParentFlightPlan
    {
        get { return _parentFlightPlan; }
        set
        {
            SetProperty(ref _parentFlightPlan, value);
            OnPropertyChanged(() => ParentFlightPlan);
        }
    } 

    //[...]

}

次に、ビューは次のようにこのプロパティにバインドできます。

<ListView ItemsSource="{Binding Path=ParentFlightPlan.Waypoints}"
          AllowDrop="True"
          DragEnter="ListViewDragEnter"
          Drop="ListViewDrop"
          >
    [...]
</ListView>

ただし、このようなビューモデルの構成は、多くの場合非常に疑わしいものです。

于 2015-11-01T15:18:41.740 に答える
0

第 2 レベルの UserControl の ViewModel に ParentDataContextProperty を設定するとどうなりますか。次に、そのユーザー コントロールに同じ名前の依存関係プロパティを作成し、xaml.cs ファイル内の VM のプロパティに値を設定します。その後、Parentcontrol はその DataContext を子コントロールの依存関係プロパティにバインドして、子 VM にその (親) データ コンテキストへのアクセスを提供できます。子コントロールは、独自の ParentDataContextProperty VM プロパティを介して親のデータ コンテキストにバインドできます。(おそらく、単に PContext または何か短い名前を付ける必要があります)。

この DependencyProperty セットアップを持つ UserControl から派生する基本クラスを作成できるため、新しいコントロールごとに記述する必要はありません。

于 2019-04-27T19:33:50.747 に答える