web-dev-qa-db-ja.com

UserControlの依存関係プロパティとMVVMのバインド

UserControlを含むMainWindowがあり、どちらもMVVMパターンで実装されています。 MainWindowVMには、UserControl1VMのプロパティにバインドしたいプロパティがあります。しかし、これは機能しません。

以下にいくつかのコードを示します(ビューモデルは、ViewModelBaseクラスにINotifyPropertyChangedを実装するある種のmvvmフレームワークを使用しますが、問題がないことを願っています)。

MainWindow.xaml:

<Window x:Class="DPandMVVM.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DPandMVVM"
    Title="MainWindow" Height="300" Width="300">
    <Grid>
        <local:UserControl1 TextInControl="{Binding Text}" />
    </Grid>
</Window>

CodeBehind MainWindow.xaml.cs:

using System.Windows;
namespace DPandMVVM
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowVM();
        }
    }
}

MainWindow-ViewModel MainWindowVM.cs:

namespace DPandMVVM
{
    public class MainWindowVM : ViewModelBase
    {
        private string _text;
        public string Text { get { return _text; } }

        public MainWindowVM()
        {
            _text = "Text from MainWindowVM";
        }
    }
}

そしてここでUserControl1.xaml:

<UserControl x:Class="DPandMVVM.UserControl1"
             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:d="http://schemas.Microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Text="{Binding TextInTextBlock}" />  
    </Grid>
</UserControl>

UserControl1.xaml.csの背後にあるコード:

using System.Windows.Controls;    
namespace DPandMVVM
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            DataContext = new UserControl1VM();
        }
    }
}

そして、Viewmodel UserControl1VM.cs:

using System.Windows;    
namespace DPandMVVM
{
    public class UserControl1VM : DependencyObject
    {
        public UserControl1VM()
        {
            TextInControl = "TextfromUserControl1VM";
        }

        public string TextInControl
        {
            get { return (string)GetValue(TextInControlProperty); }
            set { SetValue(TextInControlProperty, value); }
        }

        public static readonly DependencyProperty TextInControlProperty =
            DependencyProperty.Register("TextInControl", typeof(string), typeof(UserControl1VM));
    }
}

このコンステレーションでは、DPはMainWindow.xamlにありません。

私は何が間違っているのですか?

11
joerg

First DependencyProperty TextInControlを外部からバインドする場合は、UserControl1内で宣言する必要があります。

DPの宣言をUserControl1内に移動します。

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    public string TextInControl
    {
        get { return (string)GetValue(TextInControlProperty); }
        set { SetValue(TextInControlProperty, value); }
    }

    public static readonly DependencyProperty TextInControlProperty =
        DependencyProperty.Register("TextInControl", typeof(string), 
                                       typeof(UserControl1));
}

Second UserControlのDataContextを外部でUserControl1VMに設定しました。

    public UserControl1()
    {
        InitializeComponent();
        DataContext = new UserControl1VM(); <-- HERE (Remove this)
    }

したがって、WPFバインディングエンジンは、TextではなくUserControl1VMでプロパティMainWindowVMを探します。設定DataContextを削除し、UserControl1のXAMLを次のように更新します。

<UserControl x:Class="DPandMVVM.UserControl1"
             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:d="http://schemas.Microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="userControl1">
    <Grid>
        <TextBlock Text="{Binding TextInTextBlock, ElementName=userControl1}" />  
    </Grid>
</UserControl>

UserControlでx:Nameを設定して、ElementNameを使用してDPをバインドします。


[〜#〜] update [〜#〜]

ViewModelUserControlをそのままにしておきたい場合は、MainWindowのバインディングを更新する必要があります。次のようにバインディングでElementNameを使用して、MainWindowのDataContextでプロパティを検索するようにWPFバインディングエンジンに明示的に指示します。

<local:UserControl1 TextInControl="{Binding DataContext.Text,
                    ElementName=mainWindow}" />

このためには、ウィンドウルートレベルでx:Name="mainWindow"を設定する必要があります。

11
Rohit Vats

私には、はるかに簡単で、おそらくMVVMにより忠実であると私が信じる方法があります。

メインウィンドウのXAML:

<myNameSpace:myUserControl DataContext="{Binding Status}"/>

メインビューモデル(メインウィンドウのデータコンテキスト:

public myUserControlViewModel Status { set; get; }

これで、コンストラクターで(またはインスタンス化するときはいつでも)できます。

Status = new myUserControlViewModel();

次に、textプロパティを設定する場合:

Status.Text = "foo";

myUserControlViewModelクラス内のTextという名前のプロパティへのバインディングが設定されていることを確認してください。

<TextBox Text="{Binding Text}"/>

もちろん、プロパティがPropertyChangedを起動することを確認してください。

さらに、Resharperを使用する場合。 XAMLでUserControlのDesignインスタンスを作成して、バインディングをリンクし、次のようにしてプロパティが使用されないことを通知しないようにすることができます。

<UserControl x:Class="myNameSpace.myUserControl"
         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:d="http://schemas.Microsoft.com/expression/blend/2008"
         xmlns:myNameSpace="clr-namespace:myNameSpace"
         d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
         mc:Ignorable="d" ...>

この部分:

xmlns:myNameSpace="clr-namespace:myNameSpace"
d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
1
phillk6751

コントロールのXAMLは、DataContextを介してプロパティTextInTextBlockを参照し、DataContextはメインウィンドウのビューモデルを「ポイント」します。コントロールのデータを参照すれば完了です(ところで、その理由でDataContextを設定しないでください-バインディングは機能しなくなります):

<UserControl x:Class="DPandMVVM.UserControl1"
             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:d="http://schemas.Microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             x:Name="self">
    <Grid>
        <TextBlock Text="{Binding TextInTextBlock, ElementName=self}" />  
   </Grid>
</UserControl>
1
gomi42

これがあなたのための可能な実用的な解決策です。ただし、上記のコメントで、これはコードで機能し、おそらく(私の状況のように)デザイナでエラー(オブジェクトが見つかりません)として表示されることに注意しました。

<local:UserControl1 TextInControl="{Binding DataContext.Text,
                Source={x:Reference <<Your control that contains the DataContext here>>}}" />

ただし、設計者のミスがなく、よりクリーンなソリューションが必要です。ユーザーコントロールの依存関係プロパティを、それが含まれているウィンドウからの値に適切にバインドする方法を知りたいと思います。私が見つけたのは、(上記で示したもの以外の)何をしようとしても、 ElementNameやAncestorType/Levelなどを使用すると、デバッガーはソースが見つからないと文句を言い、ユーザーコントロールのコンテキスト内でソースを探していることを示します。それは、そのコントロールを使用してバインディングロジックを実行するときに、ユーザーコントロールコンテキストから抜け出すことができないようなものです(上記の「設計者を壊す」ソリューションを除く)。

更新:データコンテキストを持つコントロールの代わりにウィンドウを参照するように自分のソースを変更すると、状況によって問題が発生する可能性があるため、これが機能しない可能性があることに気付きました。ウィンドウを参照すると、周期的な冗長性が発生します。おそらく、問題なく機能するバインディングのソースバージョンを使用する方法を理解するでしょう。

また、ユーザーコントロールはポップアップのコンテキストで使用されるため、状況はおそらくもう少し複雑になることも付け加えておきます。

0
Prethen

これは、MVVMおよびDPバインディングを使用してUserControlsを実行する方法です。 Rohitの答えに似ていますが、若干の変更があります。基本的に、コントロールの内部ビューモデルを、UserControl自体ではなく、UserControl内のルートコンテナのDataContextに設定する必要があります。そうすれば、DPバインディングに干渉しません。

例えば。

serControl XAML

<UserControl x:Class="DPandMVVM.UserControl1"
         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:d="http://schemas.Microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         x:Name="userControl1">
<Grid x:Name="Root">
    <TextBlock Text="{Binding TextFromVM}" />  
</Grid>

serControlコードビハインド

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();            
        this.ViewModel = new UserControlVM();
    }

    public UserControlVM ViewModel
    {
        get { return this.Root.DataContext as UserControlVM ; }
        set { this.Root.DataContext = value; }
    }

    public string TextFromBinding
    {
        get { return (string)GetValue(TextFromBindingProperty); }
        set { SetValue(TextFromBindingProperty, value); }
    }

    public static readonly DependencyProperty TextFromBindingProperty =
        DependencyProperty.Register("TextFromBinding", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(null, OnTextBindingChanged));

    private static void OnTextBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var uc = d as UserControl1;
        uc.ViewModel.TextFromVM = e.NewValue as string;
    }
}

つまり、コントロールは、ViewModelであるルート要素DataContextから値を取得しますが、ViewModelは、コントロールの外部からDPバインディングを介して更新できます(この場合、親ウィンドウのViewModelへのバインディング。以下を参照)。

ウィンドウXAML

<Window  x:Class="DPandMVVM.Window1"
         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:d="http://schemas.Microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:DPandMVVM"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         x:Name="window1">
<Grid x:Name="Root">
    <local:userControl1 TextFromBinding="{Binding TextFromWindowVM}" />  
</Grid>
0
Neil Alderson