我试图创建一个简单的看板风格的应用程序在WPF,使用MVVM模式。
该板具有动态数量的列,每个列具有动态数量的任务。下面是它在 View 中的核心结构。
<Window.Content>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding TaskColumns}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Style="{StaticResource taskColumn}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource task}">
<TextBox Text="{Binding Text}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Window.Content>
此结构的 ViewModel 等价物是这些元素绑定到的ObservableCollection<ObservableCollection<Task>>
。它们在 View 中的等价物是StackPanel
和Border
。
我想使用拖放事件在两列之间传输任务(拖放边框-〉拖放堆栈面板),这本身很简单,但我不知道的是如何让ViewModel知道要传输哪些任务以及传输到哪个列。如果可能的话,可以在 View 中执行,并相应地更新 ViewModel。
我试着从父母那里找到索引来传递它们:_viewModel.TransferTask(int sourceTaskIndex, int sourceColumnIndex, int targetColumnIndex)
,但是对于这样的结构,这个解决方案很快就陷入了一个过于复杂的混乱,有时是.Parent
,有时是.TemplateParent
和(Panel)
转换,对写和读都非常混乱。
我也不知道如何用其他方法得到这些指数。用canvas获取鼠标坐标似乎是一个核心选项,但似乎并不能解决复杂性问题。
一定有更好的办法吧?
2条答案
按热度按时间pb3s4cty1#
您应该考虑将
ItemsControl
替换为ListBox
。ListBox
是一个高级的ItemsControl
,它允许滚动,并具有UI虚拟化(默认情况下启用)等性能功能。创建一个专用的控件也是非常有意义的。例如
KanbanBoard
, Package 这些列并实现拖放逻辑。只是为了封装相关的逻辑。它还将有助于维护/扩展功能和重用控件。你的设计太复杂了,为了更方便地实现拖放功能,应该进行更改。
下一步是简化数据结构。看板板反映了一个工作流,而每一列代表了一个活动。数据结构没有理由已经反映了这些列。它应该是一个简单的平面项目集合。
将项视为“物理”数据实体,而列只是抽象或抽象Map,如通过工作流转换的数据项的索引/状态。
因此,简单工作项数据模型必须具有Map到其当前工作流状态(列)的状态或索引,而不是具有反映工作流本身的数据结构。现在,当工作项根据特定的转换规则从工作流状态转换到工作流状态时,只需修改数据模型的属性以反映新状态。
例如,当转换到最后一列时,工作项的状态会更改e。例如,从“查看”到“关闭”。它仍然保留在最初的集合中。
下面的示例代码是一种伪代码,以给予类设计和类责任的概念。
这意味着您将数据与可视化分离。
客户端(e。例如视图模型类或数据模型)。..
1.将工作项集合绑定到类型为
IList
的KanbanBoard.ItemsSource
属性。KanbanBoard
不知道集合的数据类型。这是数据部分。1.使用
KanbaBoard
公开的API定义列。列由索引和名称定义。API可以是另一个
IList
类型的KanbanBoard.ColumnSource
集合属性,允许绑定另一个e。例如string
集合。每个string
表示一个列名,string
的索引Map到列的索引(可选地,KanbanBoard
可以允许定义分配给ColumnTemplate
属性的DataTemplate
,该属性支持列定义的任何数据类型)。这意味着重新排列列源集合将重新排列KanbanBoard
中的列(如果源集合是ObservableCollection
)。1.通过拖放转换一个项目。转换将更改工作项的列索引。
1.可以定义E。例如,
WorkflowItem.Index
属性(假定源集合包含一组WorkflowItem
数据模型),该属性通过KanbaBoard.ItemContainerStyle
属性绑定到项容器。这需要KanbaBoard
引入一个自定义项目容器类型KanbanBoardItem
,该类型可以扩展ListBoxItem
,并添加一个ColumnIndex
属性以启用WorkflowIndex
的模型绑定。现在,当一个项目转换时,
KanbanBoard
将相应地调整KanbaBoardItem.ColumnIndex
属性。始终在容器上工作,而不是在数据项上工作是很重要的。因此,要知道工作项在工作流中的位置,您必须阅读e。例如
WorkflowItem.Index
性质。您可以添加其他信息,例如添加State
属性等。KanbaBoardItem
(和数据模型)。KanbanBoard.cs
KanbanBoardItem。cs
状态.cs
既然已经定义了
KanbanBoard
API,我们就需要设计其内部结构。1.要显示列,我们可以使用
ListBox
控件。为了使用自定义的KanbanBoardItem
容器,我们需要扩展ListBox
以覆盖ItemsControl.GetContainerForItemOverride
和ItemsControl.PrepareContainerForItemOverride
方法。ItemsControl.GetContainerForItemOverride
只是返回我们的定制KanbanBoardItem
,然后由扩展的ListBox
使用。扩展的ListBox
,例如KanbanBoardColumn
,负责处理项目删除。如果一个项目被删除,它将设置KanbanBoardItem.Index
属性。由于附加的特性,比如header,修改ListBox
的默认Style
也是有意义的。1.我们添加一个
LsitBox´ to the
ControlTemplateof the
KanbanBoard. This
ListBoxis used to dynamically create a
KanbanBoardColumncontrols based on the
KanbanBoard。ColumnSourcecollection property. The client code can define an optional [
StyleSelector] [1](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.styleselector?view=windowsdesktop-8.0),以便单独自定义列(例如例如,对工作流状态着色)。 1.我们将
KanbanBoard.ItemContainerStyle分配给每个
KanbanBoardColumn.ItemContainerStyle属性(参见上面的
KanbanBorad类伪代码)。 1.我们将
KanbaBoard.ItemTemplate分配给每个
KanbanBoardColumn.Itemtemplate属性(参见上面的
KanbanBorad类伪代码)。 1.拖放在
KanbanBoardColumn中实现。它会在拖动开始时移除项目,并在拖放时插入项目,或者在拖动取消时重新插入项目。
KanbanBoardItemis the data payload of the drag&drop event data. On drop it also sets the ´KanbanBoardItem.ColumnIndex
完成转换。KanbaBoardColumn。cs
KanbanBoardColumnStyle。xaml
KanbanBoardStyle。xaml
使用示例
MainViewModel。cs
主窗口。xaml
kjthegm62#
使用
FrameworkElement.Tag
属性的简单解决方案:将
Tag="{Binding}"
添加到每个上下文元素允许我们通过上下文搜索必要的元素,并允许我们访问相应的ViewModel对象。VisualTreeHelper.GetParent()
消除了.Parent
/.TemplateParent
的困境。查看:
ViewModel:
下面我写了一个递归搜索必要父对象的方法。在这种情况下,它会查找所有不是 null 的标签,但是如果需要,可以用更具体的条件来替换。