View Control의 Property를 외부에서 Binding 하기 위해서는 DependencyObject 를 등록해야한다.
DependencyObject는 Control에 종속되어 있는 데이터 값이 아니라 Control 외부에 있는 데이터 저장소에 있는 값을 말한다.
이유는 View의 객체에 종속되어 있는 객체를 참조하기 보다는 binding 할 때는 여러 객체와의 데이터를 연결하기 위해서 외부 저장소에 있는 메모리를 참조하여 각자 상태를 업데이트하는 것이 효율적이기 때문인 것 같다.
그러므로 Control의 Property 를 Binding 하기 위해서 아래와 같이 DependencyObject에 등록을 한다.
이렇게 등록된 Property 의 Get,Set 은 실제로 코드 단계에서는 동작하지 않는다.
그렇기에 혹시 Get, Set동작에 따른 Action이 필요하다면 PropertyMetadata에 PropertyChangedCallback을 등록해야 한다.
UserControl Behind code
public partial class InfoPanel : UserControl
{
// DependencyObject 등록
public static DependencyProperty PatientDependency = DependencyProperty.Register
("PatientInfo", typeof(string), typeof(InfoPanel),
new PropertyMetadata("My Patient", OnPatientInfoChanged));
/// <summary>
/// Get, Set 은 직접적으로 불려지지 않는다. 다만, 외부에서 외부 View에서는 참조가 가능하다
/// 그렇기 때문에 Set, Get 동작에 따른 behind code 를 작성하면 동작하지 않을것 이다.
/// </summary>
public string PatientInfo
{
get { return (string)GetValue(PatientDependency); }
set { SetValue(PatientDependency, value); }
}
// 값이 변경되었을 때에 수행할 callback 내용이 있다면 여기에서 한다.
private static void OnPatientInfoChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var panel = source as InfoPanel;
Console.WriteLine($"NewTextChanged : {e.NewValue}");
}
public InfoPanel()
{
InitializeComponent();
}
}
위에서 등록한 Control의 Property 를 Binding 할 수 있게 된다.
아래의 코드는 MainWindow의 ViewModel에 있는 MyText Property 를 usercontrol 과 textbox에 함께 binding 한 것이다.
MainWindow.xmal
<vtkmvvm:InfoPanel x:Name="Panel3" PatientInfo="{Binding MyText}"/>
<TextBox x:Name="TextInput2" Margin="5" Height="120" Background="DarkSeaGreen"
Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}"/>
DataContext 연결 주의점
본 내용은 MVVM의 구현과 상관없이 개인적으로 Binding 하는 것을 Study 하기 위해서 테스트를 한 것을 기반으로 한 내용이다. 실제로는 구현을 MVVM 에 맞춰서 한다면 아래의 문제점을 만나지 않을 수도 있을 것이다.
UserControl을 구현하면서 UserControl에 DataContext를 설정할 경우 외부의 View에서 usercontrol을 binding 과 함께 사용하는 경우 원하는 datacontext를 보지 않을 수 있다.
위에서 구현한 InfoPanel의 xaml 파일을 보면 UserControl에 MVVM 에 따라 DataContext로 ViewModel을 binding 하였다.
<InfoPanel.xaml>
<UserControl
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:VtkMvvm" x:Class="VtkMvvm.InfoPanel"
DataContext="{Binding Source={StaticResource Locator}, Path=InfoPanelVM}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Width="300">
<StackPanel Orientation="Horizontal" >
<TextBlock Width="100" Text="BindToBehind" VerticalAlignment="Center"/>
<!-- Behind Code Property binding -->
<TextBlock x:Name="Title2" Margin="5" DataContext="{Binding PatientInfo,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:InfoPanel}}}"
Text="{Binding}" Width="200" Height="100" FontSize="25"
TextAlignment="Center" Foreground="DarkGreen" Background="SkyBlue" TextWrapping="Wrap"/>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
이런 경우에는 MainWindow에서 UserControl의 PatientInfo Property 를 binding 할 때 MainWindow 의 상위단에서 지정한 DataContext가 아니라 UserControl의 DataContext로 변경되면서 워하는 Property 와 binding 을 하지 못하게 된다.
<Window x:Class="VtkMvvm.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:vtk="clr-namespace:Kitware.VTK;assembly=Kitware.VTK"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:vtkmvvm="clr-namespace:VtkMvvm"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
mc:Ignorable="d"
Title="MainWindow" Height="750" Width="800"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Orientation="Vertical" VerticalAlignment="Center">
<!-- 내부의 ViewModel과의 DataContext binding 이 안됨 -->
<vtkmvvm:InfoPanel x:Name="Panel3" PatientInfo="{Binding MyText}"/>
</StackPanel>
<StackPanel VerticalAlignment="Center" Grid.Column="0">
<TextBox x:Name="TextInput2" Margin="5" Height="120" Background="DarkSeaGreen" Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Grid>
</Window>
이런 경우 Binding 할 때에 Control 의 DataContext를 아래와 같이 위치를 변경하면 문제 없어진다.
<Grid>
<Grid.DataContext>
<Binding Source="{StaticResource Locator}" Path="InfoPanelVM"/>
</Grid.DataContext>
</Grid>
View에서 Behind Property binding 하기
UserControl의 Property에서 Binding 하는 방법은 여러가지 있으나 방법 4의 내용이 사용에 따라 제일 안전하다고 생각된다.
방법 1. => InfoPanel DataContext가 안되어 있는경우 가능
<vtkmvvm:InfoPanel x:Name="Panel3" PatientInfo="{Binding MyText}"/>
방법 2. => InfoPanel DataContext를 덮어버림, 위에서 지정한 DataContext를 기반으로 함
<vtkmvvm:InfoPanel x:Name="Panel3" PatientInfo="{Binding}" DataContext="{Binding MyText}"/>
방법 3. => InfoPanel DataContext를 덮어버림, DataContext의 source를 직접지정
<vtkmvvm:InfoPanel x:Name="Panel3" PatientInfo="{Binding MyText}"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"/>
방법 4. => 내부의 DataContext를 변경하지는 않고 PatientInfo에 대해서만 binding을 수행함[OK]
<vtkmvvm:InfoPanel x:Name="Panel3" PatientInfo="{Binding Source={StaticResource Locator}, Path=Main.MyText}"/>