更新自定义控件的属性单击
Update Properties on Custom Control click
我正在使用 WPF(尤其是 MVVM 模式),并且正在尝试创建一个显示任务列表的简单应用程序。我创建了一个名为 TaskListControl
的自定义控件,它显示了一个名为 TaskListItemControl
的其他自定义控件的列表,并且每个控件都有自己的 ViewModel。
这是 TaskListItemControl
模板,当 IsSelected 设置为 true
时,您可以在其中看到影响控件外观的 InputBindings
和 Triggers
:
<UserControl x:Class="CSB.Tasks.TaskListItemControl"
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:CSB.Tasks"
mc:Ignorable="d"
d:DesignHeight="70"
d:DesignWidth="400">
<!-- Custom control that represents a Task. -->
<UserControl.Resources>
<!-- The control style. -->
<Style x:Key="ContentStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Border x:Name="ContainerBorder" BorderBrush="{StaticResource LightVoidnessBrush}"
Background="{StaticResource VoidnessBrush}"
BorderThickness="1"
Margin="2">
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding SelctTaskCommand}"/>
</Border.InputBindings>
<!-- The grid that contains the control. -->
<Grid Name="ContainerGrid" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Border representing the priority state of the Task:
The color is defined by a ValueConverter according to the PriorityLevel of the Task object. -->
<Border Grid.Column="0"
Width="10"
Background="{Binding Priority, Converter={local:PriorityLevelToRGBConverter}}">
</Border>
<!-- Border containing the Task's informations. -->
<Border Grid.Column="1" Padding="5">
<StackPanel>
<!-- The title of the Task. -->
<TextBlock Text="{Binding Title}" FontSize="{StaticResource TaskListItemTitleFontSize}" Foreground="{StaticResource DirtyWhiteBrush}"/>
<!-- The customer the Taks refers to. -->
<TextBlock Text="{Binding Customer}" Style="{StaticResource TaskListItemControlCustomerTextBlockStyle}"/>
<!-- The description of the Task. -->
<TextBlock Text="{Binding Description}"
TextTrimming="WordEllipsis"
Foreground="{StaticResource DirtyWhiteBrush}"/>
</StackPanel>
</Border>
<!-- Border that contains the controls for the Task management. -->
<Border Grid.Column="2"
Padding="5">
<!-- Selection checkbox of the Task. -->
<CheckBox Grid.Column="2" VerticalAlignment="Center"/>
</Border>
</Grid>
</Border>
<!-- Template triggers. -->
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" TargetName="ContainerBorder" Value="{StaticResource OverlayVoidnessBrush}"/>
<Setter Property="BorderBrush" TargetName="ContainerBorder" Value="{StaticResource PeterriverBrush}"/>
</DataTrigger>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0:0" To="{StaticResource OverlayVoidness}" Storyboard.TargetName="ContainerGrid" Storyboard.TargetProperty="Background.Color"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0:0" To="Transparent" Storyboard.TargetName="ContainerGrid" Storyboard.TargetProperty="Background.Color"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<!-- Content of the control: assignment of the DataContext for design-time testing. -->
<ContentControl d:DataContext="{x:Static local:TaskListItemDesignModel.Instance}"
Style="{StaticResource ContentStyle}"/>
这里是 TaskListItemViewModel
应该执行 Action
的地方(所有 PropertyChanged 样板代码都在 BaseViewModel class 中处理):
/// <summary>
/// The ViewModel for the <see cref="TaskListItemControl"/>.
/// </summary>
public class TaskListItemViewModel : BaseViewModel
{
#region Public Properties
/// <summary>
/// Priority level of the task.
/// </summary>
public PriorityLevel Priority { get; set; }
/// <summary>
/// The name of the task.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The customer the task refers to.
/// </summary>
public string Customer { get; set; }
/// <summary>
/// The description of the task.
/// </summary>
public string Description { get; set; }
/// <summary>
/// True if the Task is the selected one in the task list.
/// </summary>
public bool IsSelected { get; set; }
#endregion
#region Commands
/// <summary>
/// The command fired whenever a task is selected.
/// </summary>
public ICommand SelectTaskCommand { get; set; }
#endregion
#region Constructor
/// <summary>
/// The <see cref="TaskListItemViewModel"/> default constructor.
/// </summary>
public TaskListItemViewModel()
{
// Initialize commands.
// When the task is selected, IsSelected becomes true.
// The command will do other stuff in the future.
SelectTaskCommand = new RelayCommand(() => IsSelected = true);
}
#endregion
}
数据是通过绑定到 TaskListControl
控件的设计模型提供的,其中列表的每个项目的属性都是硬编码的(此设计模型将被数据库替换,因为此 class仅提供虚拟数据):
/// <summary>
/// The <see cref="TaskListControl"/> design model that provides dummy data for the XAML testing.
/// </summary>
public class TaskListDesignModel : TaskListViewModel
{
#region Public Properties
/// <summary>
/// A single instance of the <see cref="TaskListDesignModel"/> class.
/// </summary>
public static TaskListDesignModel Instance => new TaskListDesignModel();
#endregion
#region Constructor
/// <summary>
/// The <see cref="TaskListDesignModel"/> default constructor that provides dummy data.
/// </summary>
public TaskListDesignModel()
{
Items = new ObservableCollection<TaskListItemViewModel>
{
new TaskListItemViewModel
{
Title = "Activity #1",
Customer = "Internal",
Description = "This is activity #1.",
Priority = PriorityLevel.High,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #2",
Customer = "Internal",
Description = "This is activity #2.",
Priority = PriorityLevel.High,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #3",
Customer = "Internal",
Description = "This is activity #3.",
Priority = PriorityLevel.High,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #4",
Customer = "Internal",
Description = "This is activity #4.",
Priority = PriorityLevel.Medium,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #5",
Customer = "Internal",
Description = "This is activity #5.",
Priority = PriorityLevel.Medium,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #6",
Customer = "Internal",
Description = "This is activity #6.",
Priority = PriorityLevel.Low,
IsSelected = false
}
};
}
#endregion
}
结果如下:
选择列表项时我想做的是在ViewModel中更新他的IsSelected
属性并改变它通过 Triggers
出现,但当我点击某个项目时没有任何反应。
Here 是 link 到整个项目的 GitHub 存储库(如果需要)。
我错过了什么?预先感谢您的帮助。
你应该解决这两个问题来解决选择问题:
1) 我在您的 TaskListItemControl
中发现了一个拼写错误:
命令绑定上的第 25 行应该是 "SelectTaskCommand"。
这将最终调用命令。
2) 然后在您的 TaskListItemViewModel
中,您必须让您的属性引发 PropertyChanged
事件。我强烈建议您对所有 ViewModel 属性使用此方法。但是要解决您的问题,这必须至少适用于 IsSelected
属性:
private bool isSelected;
public bool IsSelected
{
get => this.isSelected;
set
{
if (value.Equals(this.isSelected)
{
return;
}
this.isSelected = value;
OnPropertyChanged();
}
}
这将传播对 IsSelected
的任何更改,因此 DataTrigger 可以按预期工作。
另一个建议是将 BaseViewModel
中的 PropertyChanged
调用者签名修改为:
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
这样您就可以避免总是将 属性 名称作为参数传递。
如果您希望 CheckBox 反映选择状态并且应用于撤消选择,只需将 TwoWay 绑定添加到其 IsChecked 属性:
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" />
我正在使用 WPF(尤其是 MVVM 模式),并且正在尝试创建一个显示任务列表的简单应用程序。我创建了一个名为 TaskListControl
的自定义控件,它显示了一个名为 TaskListItemControl
的其他自定义控件的列表,并且每个控件都有自己的 ViewModel。
这是 TaskListItemControl
模板,当 IsSelected 设置为 true
时,您可以在其中看到影响控件外观的 InputBindings
和 Triggers
:
<UserControl x:Class="CSB.Tasks.TaskListItemControl"
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:CSB.Tasks"
mc:Ignorable="d"
d:DesignHeight="70"
d:DesignWidth="400">
<!-- Custom control that represents a Task. -->
<UserControl.Resources>
<!-- The control style. -->
<Style x:Key="ContentStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Border x:Name="ContainerBorder" BorderBrush="{StaticResource LightVoidnessBrush}"
Background="{StaticResource VoidnessBrush}"
BorderThickness="1"
Margin="2">
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding SelctTaskCommand}"/>
</Border.InputBindings>
<!-- The grid that contains the control. -->
<Grid Name="ContainerGrid" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Border representing the priority state of the Task:
The color is defined by a ValueConverter according to the PriorityLevel of the Task object. -->
<Border Grid.Column="0"
Width="10"
Background="{Binding Priority, Converter={local:PriorityLevelToRGBConverter}}">
</Border>
<!-- Border containing the Task's informations. -->
<Border Grid.Column="1" Padding="5">
<StackPanel>
<!-- The title of the Task. -->
<TextBlock Text="{Binding Title}" FontSize="{StaticResource TaskListItemTitleFontSize}" Foreground="{StaticResource DirtyWhiteBrush}"/>
<!-- The customer the Taks refers to. -->
<TextBlock Text="{Binding Customer}" Style="{StaticResource TaskListItemControlCustomerTextBlockStyle}"/>
<!-- The description of the Task. -->
<TextBlock Text="{Binding Description}"
TextTrimming="WordEllipsis"
Foreground="{StaticResource DirtyWhiteBrush}"/>
</StackPanel>
</Border>
<!-- Border that contains the controls for the Task management. -->
<Border Grid.Column="2"
Padding="5">
<!-- Selection checkbox of the Task. -->
<CheckBox Grid.Column="2" VerticalAlignment="Center"/>
</Border>
</Grid>
</Border>
<!-- Template triggers. -->
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" TargetName="ContainerBorder" Value="{StaticResource OverlayVoidnessBrush}"/>
<Setter Property="BorderBrush" TargetName="ContainerBorder" Value="{StaticResource PeterriverBrush}"/>
</DataTrigger>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0:0" To="{StaticResource OverlayVoidness}" Storyboard.TargetName="ContainerGrid" Storyboard.TargetProperty="Background.Color"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="0:0:0:0" To="Transparent" Storyboard.TargetName="ContainerGrid" Storyboard.TargetProperty="Background.Color"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<!-- Content of the control: assignment of the DataContext for design-time testing. -->
<ContentControl d:DataContext="{x:Static local:TaskListItemDesignModel.Instance}"
Style="{StaticResource ContentStyle}"/>
这里是 TaskListItemViewModel
应该执行 Action
的地方(所有 PropertyChanged 样板代码都在 BaseViewModel class 中处理):
/// <summary>
/// The ViewModel for the <see cref="TaskListItemControl"/>.
/// </summary>
public class TaskListItemViewModel : BaseViewModel
{
#region Public Properties
/// <summary>
/// Priority level of the task.
/// </summary>
public PriorityLevel Priority { get; set; }
/// <summary>
/// The name of the task.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The customer the task refers to.
/// </summary>
public string Customer { get; set; }
/// <summary>
/// The description of the task.
/// </summary>
public string Description { get; set; }
/// <summary>
/// True if the Task is the selected one in the task list.
/// </summary>
public bool IsSelected { get; set; }
#endregion
#region Commands
/// <summary>
/// The command fired whenever a task is selected.
/// </summary>
public ICommand SelectTaskCommand { get; set; }
#endregion
#region Constructor
/// <summary>
/// The <see cref="TaskListItemViewModel"/> default constructor.
/// </summary>
public TaskListItemViewModel()
{
// Initialize commands.
// When the task is selected, IsSelected becomes true.
// The command will do other stuff in the future.
SelectTaskCommand = new RelayCommand(() => IsSelected = true);
}
#endregion
}
数据是通过绑定到 TaskListControl
控件的设计模型提供的,其中列表的每个项目的属性都是硬编码的(此设计模型将被数据库替换,因为此 class仅提供虚拟数据):
/// <summary>
/// The <see cref="TaskListControl"/> design model that provides dummy data for the XAML testing.
/// </summary>
public class TaskListDesignModel : TaskListViewModel
{
#region Public Properties
/// <summary>
/// A single instance of the <see cref="TaskListDesignModel"/> class.
/// </summary>
public static TaskListDesignModel Instance => new TaskListDesignModel();
#endregion
#region Constructor
/// <summary>
/// The <see cref="TaskListDesignModel"/> default constructor that provides dummy data.
/// </summary>
public TaskListDesignModel()
{
Items = new ObservableCollection<TaskListItemViewModel>
{
new TaskListItemViewModel
{
Title = "Activity #1",
Customer = "Internal",
Description = "This is activity #1.",
Priority = PriorityLevel.High,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #2",
Customer = "Internal",
Description = "This is activity #2.",
Priority = PriorityLevel.High,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #3",
Customer = "Internal",
Description = "This is activity #3.",
Priority = PriorityLevel.High,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #4",
Customer = "Internal",
Description = "This is activity #4.",
Priority = PriorityLevel.Medium,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #5",
Customer = "Internal",
Description = "This is activity #5.",
Priority = PriorityLevel.Medium,
IsSelected = false
},
new TaskListItemViewModel
{
Title = "Activity #6",
Customer = "Internal",
Description = "This is activity #6.",
Priority = PriorityLevel.Low,
IsSelected = false
}
};
}
#endregion
}
结果如下:
选择列表项时我想做的是在ViewModel中更新他的IsSelected
属性并改变它通过 Triggers
出现,但当我点击某个项目时没有任何反应。
Here 是 link 到整个项目的 GitHub 存储库(如果需要)。
我错过了什么?预先感谢您的帮助。
你应该解决这两个问题来解决选择问题:
1) 我在您的 TaskListItemControl
中发现了一个拼写错误:
命令绑定上的第 25 行应该是 "SelectTaskCommand"。
这将最终调用命令。
2) 然后在您的 TaskListItemViewModel
中,您必须让您的属性引发 PropertyChanged
事件。我强烈建议您对所有 ViewModel 属性使用此方法。但是要解决您的问题,这必须至少适用于 IsSelected
属性:
private bool isSelected;
public bool IsSelected
{
get => this.isSelected;
set
{
if (value.Equals(this.isSelected)
{
return;
}
this.isSelected = value;
OnPropertyChanged();
}
}
这将传播对 IsSelected
的任何更改,因此 DataTrigger 可以按预期工作。
另一个建议是将 BaseViewModel
中的 PropertyChanged
调用者签名修改为:
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
这样您就可以避免总是将 属性 名称作为参数传递。
如果您希望 CheckBox 反映选择状态并且应用于撤消选择,只需将 TwoWay 绑定添加到其 IsChecked 属性:
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" />