رافعی مهدی
دوشنبه 12 فروردین 1392, 18:21 عصر
با سلام
دو تا UserControl دارم که یکی از اونها داخل دیگری استفاده میشه. (ChildUserControl و ParentUserControl) پراپرتیهای کنترل Child از نظر استفاده به دو دسته تقسیم میشوند. پراپرتی هایی که مستقیماً نیاز هست مقدار دهی بشوند و پراپرتیهایی که کنترل Parent اونها رو مدیریت (مقداردهی) میکنه. این کار رو قصد دارم با استفاده از الگوی MVVM انجام بدهم، اما ایده درستی برای انجام این کار ندارم و نمیدونم باید از چه روشی استفاده کنم؟
________________________________________
برای واضحتر شدن سوال، مثال بسیار ساده ولی کاملی رو مطرح میکنم. در اینجا برای سادگی، کنترل Child فقط دو تا پراپرتی داره. پراپرتی IsSelected رو باید ParentUserControl مدیریت کنه و پراپرتی PathData رو مستقیماً باید مقداردهی کرد. همچنین، ParentUserControl فقط یک پراپرتی براش درنظر گرفته شده تا صورت این سوال رو پیچیده نکنه.
کنترل فرزند (داخلی):
<UserControl x:Class="NestedUserControls_MVVM.View.ChildUserControl"
xmlns:v="clr-namespace:NestedUserControls_MVVM.View"
...>
<UserControl.Resources>
<Style TargetType="{x:Type v:ChildUserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type v:ChildUserControl}">
<Border x:Name="brdRoot" Background="#01FFFFFF" Margin="5,3" Height="16" Width="16">
<Path x:Name="pathShape" Stretch="Uniform" Stroke="Gray" Fill="Red" Data="{Binding PathData}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="brdRoot" Property="IsMouseOver" Value="True">
<Setter TargetName="pathShape" Property="Stroke" Value="Black"/>
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="False">
<Setter TargetName="pathShape" Property="Fill" Value="Transparent"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="pathShape" Property="Fill" Value="Gray"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
مدل نمایش این کنترل:
public class ChildUserControlViewModel : DependencyObject
{
#region ________________________________________ IsSelected
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected",
typeof(bool),
typeof(ChildUserControlViewModel),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));
#endregion
#region ________________________________________ PathData
public Geometry PathData
{
get { return (Geometry)GetValue(PathDataProperty); }
set { SetValue(PathDataProperty, value); }
}
public static readonly DependencyProperty PathDataProperty =
DependencyProperty.Register("PathData",
typeof(Geometry),
typeof(ChildUserControlViewModel),
new FrameworkPropertyMetadata(Geometry.Empty, FrameworkPropertyMetadataOptions.None));
#endregion
}
کنترل والد (خارجی-دربرگیرنده):
<UserControl x:Class="NestedUserControls_MVVM.View.ParentUserControl"
xmlns:v="clr-namespace:NestedUserControls_MVVM.View"
xmlns:vm="clr-namespace:NestedUserControls_MVVM.ViewModel"
...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<v:ChildUserControl Grid.Column="0">
<v:ChildUserControl.DataContext>
<!-- Setting explicit value to VM-DPs is OK. -->
<vm:ChildUserControlViewModel
IsSelected="True"
PathData="M9.227273,0.5 L11.5,0.5 11.5,11.5 9.227273,11.5 z M4.8636366,0.5 L7.1363636,0.5 7.1363636,11.5 4.8636366,11.5 z M0.5,0.5 L2.7727273,0.5 2.7727273,11.5 0.5,11.5 z"/>
</v:ChildUserControl.DataContext>
</v:ChildUserControl>
<v:ChildUserControl Grid.Column="1">
<v:ChildUserControl.DataContext>
<!-- Binding any VM-DP causes error. -->
<vm:ChildUserControlViewModel
IsSelected="{Binding ArePathsSelected}"
PathData="M0.5,9.227273 L11.5,9.227273 11.5,11.5 0.5,11.5 z M0.5,4.8636367 L11.5,4.8636367 11.5,7.1363637 0.5,7.1363637 z M0.5,0.5 L11.5,0.5 11.5,2.7727275 0.5,2.7727275 z"/>
</v:ChildUserControl.DataContext>
</v:ChildUserControl>
</Grid>
</UserControl>
مدل نمایش این کنترل:
public class ParentUserControlViewModel : DependencyObject
{
#region ________________________________________ ArePathsSelected
public bool ArePathsSelected
{
get { return (bool)GetValue(ArePathsSelectedProperty); }
set { SetValue(ArePathsSelectedProperty, value); }
}
public static readonly DependencyProperty ArePathsSelectedProperty =
DependencyProperty.Register("ArePathsSelected",
typeof(bool),
typeof(ParentUserControlViewModel),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));
#endregion
}
نحوه استفاده از این کنترلها (مثلاً در یک Window)
<Grid>
<StackPanel VerticalAlignment="Center">
<v:ParentUserControl HorizontalAlignment="Center" VerticalAlignment="Center">
<v:ParentUserControl.DataContext>
<vm:ParentUserControlViewModel ArePathsSelected="True"/>
</v:ParentUserControl.DataContext>
</v:ParentUserControl>
<v:ParentUserControl HorizontalAlignment="Center" VerticalAlignment="Center">
<v:ParentUserControl.DataContext>
<vm:ParentUserControlViewModel ArePathsSelected="False"/>
</v:ParentUserControl.DataContext>
</v:ParentUserControl>
</StackPanel>
</Grid>
چیزی که الان داریم:
102176
چیزی که انتظار میره داشته باشیم:
102175
همونطور که به صورت کامنت هم مشخص کردم، اضافه کردن عبارت
IsSelected="{Binding ArePathsSelected}"
در کنترل والد (XAML)، باعث ایجاد خطا میشه که در پنجره Output این پیام رو میده:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ArePathsSelected; DataItem=null; target element is 'ChildUserControlViewModel' (HashCode=13040701); target property is 'IsSelected' (type 'Boolean')
این پست برای سریع تر به پاسخ رسیدن به صورت موازی در اینجا (http://stackoverflow.com/questions/15741515/nested-usercontrols-in-mvvm-with-different-dps-usage) هم در جریان هست.
دو تا UserControl دارم که یکی از اونها داخل دیگری استفاده میشه. (ChildUserControl و ParentUserControl) پراپرتیهای کنترل Child از نظر استفاده به دو دسته تقسیم میشوند. پراپرتی هایی که مستقیماً نیاز هست مقدار دهی بشوند و پراپرتیهایی که کنترل Parent اونها رو مدیریت (مقداردهی) میکنه. این کار رو قصد دارم با استفاده از الگوی MVVM انجام بدهم، اما ایده درستی برای انجام این کار ندارم و نمیدونم باید از چه روشی استفاده کنم؟
________________________________________
برای واضحتر شدن سوال، مثال بسیار ساده ولی کاملی رو مطرح میکنم. در اینجا برای سادگی، کنترل Child فقط دو تا پراپرتی داره. پراپرتی IsSelected رو باید ParentUserControl مدیریت کنه و پراپرتی PathData رو مستقیماً باید مقداردهی کرد. همچنین، ParentUserControl فقط یک پراپرتی براش درنظر گرفته شده تا صورت این سوال رو پیچیده نکنه.
کنترل فرزند (داخلی):
<UserControl x:Class="NestedUserControls_MVVM.View.ChildUserControl"
xmlns:v="clr-namespace:NestedUserControls_MVVM.View"
...>
<UserControl.Resources>
<Style TargetType="{x:Type v:ChildUserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type v:ChildUserControl}">
<Border x:Name="brdRoot" Background="#01FFFFFF" Margin="5,3" Height="16" Width="16">
<Path x:Name="pathShape" Stretch="Uniform" Stroke="Gray" Fill="Red" Data="{Binding PathData}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="brdRoot" Property="IsMouseOver" Value="True">
<Setter TargetName="pathShape" Property="Stroke" Value="Black"/>
</Trigger>
<DataTrigger Binding="{Binding IsSelected}" Value="False">
<Setter TargetName="pathShape" Property="Fill" Value="Transparent"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="pathShape" Property="Fill" Value="Gray"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
مدل نمایش این کنترل:
public class ChildUserControlViewModel : DependencyObject
{
#region ________________________________________ IsSelected
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected",
typeof(bool),
typeof(ChildUserControlViewModel),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));
#endregion
#region ________________________________________ PathData
public Geometry PathData
{
get { return (Geometry)GetValue(PathDataProperty); }
set { SetValue(PathDataProperty, value); }
}
public static readonly DependencyProperty PathDataProperty =
DependencyProperty.Register("PathData",
typeof(Geometry),
typeof(ChildUserControlViewModel),
new FrameworkPropertyMetadata(Geometry.Empty, FrameworkPropertyMetadataOptions.None));
#endregion
}
کنترل والد (خارجی-دربرگیرنده):
<UserControl x:Class="NestedUserControls_MVVM.View.ParentUserControl"
xmlns:v="clr-namespace:NestedUserControls_MVVM.View"
xmlns:vm="clr-namespace:NestedUserControls_MVVM.ViewModel"
...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<v:ChildUserControl Grid.Column="0">
<v:ChildUserControl.DataContext>
<!-- Setting explicit value to VM-DPs is OK. -->
<vm:ChildUserControlViewModel
IsSelected="True"
PathData="M9.227273,0.5 L11.5,0.5 11.5,11.5 9.227273,11.5 z M4.8636366,0.5 L7.1363636,0.5 7.1363636,11.5 4.8636366,11.5 z M0.5,0.5 L2.7727273,0.5 2.7727273,11.5 0.5,11.5 z"/>
</v:ChildUserControl.DataContext>
</v:ChildUserControl>
<v:ChildUserControl Grid.Column="1">
<v:ChildUserControl.DataContext>
<!-- Binding any VM-DP causes error. -->
<vm:ChildUserControlViewModel
IsSelected="{Binding ArePathsSelected}"
PathData="M0.5,9.227273 L11.5,9.227273 11.5,11.5 0.5,11.5 z M0.5,4.8636367 L11.5,4.8636367 11.5,7.1363637 0.5,7.1363637 z M0.5,0.5 L11.5,0.5 11.5,2.7727275 0.5,2.7727275 z"/>
</v:ChildUserControl.DataContext>
</v:ChildUserControl>
</Grid>
</UserControl>
مدل نمایش این کنترل:
public class ParentUserControlViewModel : DependencyObject
{
#region ________________________________________ ArePathsSelected
public bool ArePathsSelected
{
get { return (bool)GetValue(ArePathsSelectedProperty); }
set { SetValue(ArePathsSelectedProperty, value); }
}
public static readonly DependencyProperty ArePathsSelectedProperty =
DependencyProperty.Register("ArePathsSelected",
typeof(bool),
typeof(ParentUserControlViewModel),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.None));
#endregion
}
نحوه استفاده از این کنترلها (مثلاً در یک Window)
<Grid>
<StackPanel VerticalAlignment="Center">
<v:ParentUserControl HorizontalAlignment="Center" VerticalAlignment="Center">
<v:ParentUserControl.DataContext>
<vm:ParentUserControlViewModel ArePathsSelected="True"/>
</v:ParentUserControl.DataContext>
</v:ParentUserControl>
<v:ParentUserControl HorizontalAlignment="Center" VerticalAlignment="Center">
<v:ParentUserControl.DataContext>
<vm:ParentUserControlViewModel ArePathsSelected="False"/>
</v:ParentUserControl.DataContext>
</v:ParentUserControl>
</StackPanel>
</Grid>
چیزی که الان داریم:
102176
چیزی که انتظار میره داشته باشیم:
102175
همونطور که به صورت کامنت هم مشخص کردم، اضافه کردن عبارت
IsSelected="{Binding ArePathsSelected}"
در کنترل والد (XAML)، باعث ایجاد خطا میشه که در پنجره Output این پیام رو میده:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ArePathsSelected; DataItem=null; target element is 'ChildUserControlViewModel' (HashCode=13040701); target property is 'IsSelected' (type 'Boolean')
این پست برای سریع تر به پاسخ رسیدن به صورت موازی در اینجا (http://stackoverflow.com/questions/15741515/nested-usercontrols-in-mvvm-with-different-dps-usage) هم در جریان هست.