本月我是WPF和MVVM设计模式的新手,而且一般来说不仅仅是一些实践。为了学习,我一直在玩文本框,矩形以及在窗口中显示它们的方法。我从Ashley Davis出色的“WPF中的简单拖动选择”教程开始,该教程介绍了为一组矩形创建视图模型,将列表框绑定到所述集合,以及使用画布为列表框设置样式,为其创建数据模板。矩形,以及基本的“橡皮筋”选择逻辑。
我已经在本教程的基础上改进了拖动选择,使其行为更像是在Windows资源管理器中的选择,并允许从角落或边缘调整矩形的大小。
一切顺利,直到我更改了MainWindow.xaml,试图在侧面包含一个列用于各种按钮和控件,从而将“编辑器表面”网格从主窗口上的1x1网格内移动到一列一个1x2网格,将数据模板移动到网格的资源(因为它将是窗口中唯一需要它的元素)。一旦我这样做,我写的与列表框交互的子程序开始行为不端 - 橡皮筋选择不再起作用。没有可视指示正在选择列表框项目(它们之前已突出显示),并且在拖动选择mouseUp事件返回零之后询问listBox.SelectedItems.Count。
经过一些实验,在本网站和WPF Unleashed书的部分阅读了很多问题,并查看了msdn数据绑定概述,截至今天早上我还是找不到我的错误。我认为这是一个数据绑定错误或不正确的数据上下文。
有关视图模型的一些细节:
DataFieldViewModel
...实现INotifyPropertyChanged并公开其属性(在本例中为矩形和文本框)X,Y位置,宽度,高度,可见性和选择状态(在多个橡皮筋选择操作中跟踪它的方法)
PageViewModel
...实现了INotifyPropertyChanged,并且除其他外还有一个名为DataFields的DataFieldViewModel类型的ObservableCollection,并将其公开为ReadOnly属性。
以下是MainWindow.xaml.vb以及其中一个破损的潜艇:
Namespace EditorUI
'
' The main window of the editor.
'
Partial Public Class MainWindow
Inherits Window
'
' Temporary. Will be replaced with a collection of pages eventually
'
Private Pages As PageViewModel
(其余数据成员和属性为简洁而剪裁)
Public Sub New()
InitializeComponent()
Pages = New PageViewModel
End Sub
(这是其中一个有问题的潜艇)
'
' Select all the data fields that intersect the selection rectangle.
' Remove any selected data fields which do not.
'
Private Sub ApplyDragSelectionRectangle()
If (LeftMouseDrag) Then
Dim selectionRectangle As New Rect(Canvas.GetLeft(selectionRectangleBorder), _
Canvas.GetTop(selectionRectangleBorder), _
selectionRectangleBorder.Width, _
selectionRectangleBorder.Height)
'
' Find and select all the list box items.
'
For Each dataFieldViewModel As DataFieldViewModel In Me.Pages.GetDataFields
Dim hitBox As New Rect(dataFieldViewModel.hbX, _
dataFieldViewModel.hbY, _
dataFieldViewModel.hbWidth, _
dataFieldViewModel.hbHeight)
If (selectionRectangle.IntersectsWith(hitBox)) Then
If (dataFieldViewModel.ExistingSelection) Then
'
' data field is already part of an existing selection; unselect it
'
Me.DataFieldListBox.SelectedItems.Remove(dataFieldViewModel)
Else
Me.DataFieldListBox.SelectedItems.Add(dataFieldViewModel)
End If
End If
If Not (selectionRectangle.IntersectsWith(hitBox)) Then
If (dataFieldViewModel.ExistingSelection) Then
'
' data field was part of an existing selection; reselect it
'
Me.DataFieldListBox.SelectedItems.Add(dataFieldViewModel)
Else
Me.DataFieldListBox.SelectedItems.Remove(dataFieldViewModel)
End If
End If
Next
Else
dragSelectionCanvas.Visibility = Visibility.Collapsed
'
' update all data fields' existing selection status to the new
' selection (first set them all to false to catch data fields
' that were removed)
'
For Each dataFieldViewModel As DataFieldViewModel In Me.DataFieldListBox.Items
dataFieldViewModel.ExistingSelection = False
Next
For Each dataFieldViewModel As DataFieldViewModel In Me.DataFieldListBox.SelectedItems
dataFieldViewModel.ExistingSelection = True
Next
End If
End Sub
最后,这是XAML的全部内容:
<Window x:Class="EditorUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Editor_UI_Experiments.EditorUI"
Title="Editor UI Experiments"
Width="900"
Height="600"
Loaded="Window_Loaded"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="778*" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
VerticalAlignment="Top"
Height="25"
Content="Explode :D"
Name="Button1"
/>
<Grid
Name="EditorSurface"
Grid.Column="1"
MouseDown="Editor_MouseDown"
MouseUp="Editor_MouseUp"
MouseMove="Editor_MouseMove"
>
<Grid.Background>
<ImageBrush ImageSource="{Binding GetPageImage}" />
</Grid.Background>
<Grid.DataContext>
<local:PageViewModel/>
</Grid.DataContext>
<Grid.Resources>
<!--
A data template that defines the visuals for a data field.
-->
<DataTemplate
DataType="{x:Type local:DataFieldViewModel}"
>
<!--
The data field is embedded in a Grid so that we can set the Margin
The margin is set so that the ListBox item selection fits nicely around the Rectangle.
-->
<Grid
Margin="0,2,2,2"
>
<!--
text box where the data field's response lives (it could be a Database tag,
or a check mark, or custom response)
-->
<TextBox
Width="{Binding Width}"
Height="{Binding Height}"
Background="LightBlue"
Cursor="IBeam"
MouseDown="TextBox_MouseDown"
MouseUp="TextBox_MouseUp"
MouseMove="TextBox_MouseMove"
Text="Example Text"
/>
<!--
rectangle that lives on top of the text field to aid in positioning the data field
-->
<Rectangle
Width="{Binding Width}"
Height="{Binding Height}"
Stroke="LightBlue"
StrokeThickness="5"
Fill="White"
Opacity="0.5"
Cursor="SizeAll"
MouseDown="Rectangle_MouseDown"
MouseUp="Rectangle_MouseUp"
MouseMove="Rectangle_MouseMove"
Visibility="{Binding Visibility}"
/>
<!--
Thumb "handles" to give the user a way to resize the data field
-->
<!--
These four live in the corners of a data field and allow resizing on
X and Y simultaneously
-->
<Rectangle
Width="7"
Height="7"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="-1,-1,0,0"
Cursor="SizeNWSE"
Fill="LightGray"
Stroke="Gray"
Opacity="0.6"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
<Rectangle
Width="7"
Height="7"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="0,-1,-1,0"
Cursor="SizeNESW"
Fill="LightGray"
Stroke="Gray"
Opacity="0.6"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
<Rectangle
Width="7"
Height="7"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Margin="-1,0,0,-1"
Cursor="SizeNESW"
Fill="LightGray"
Stroke="Gray"
Opacity="0.6"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
<Rectangle
Width="7"
Height="7"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Margin="0,0,-1,-1"
Cursor="SizeNWSE"
Fill="LightGray"
Stroke="Gray"
Opacity="0.6"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
<!--
These four live along the data field's edges and allow resizing in the X
or Y direction only. They have zero opacity to avoid visual clutter
-->
<Rectangle
Height="5"
VerticalAlignment="Top"
HorizontalAlignment="Stretch"
Margin="7,0,7,0"
Cursor="SizeNS"
Fill="Yellow"
Opacity="0"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
<Rectangle
Height="5"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Margin="7,0,7,0"
Cursor="SizeNS"
Fill="Yellow"
Opacity="0"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
<Rectangle
Width="5"
VerticalAlignment="Stretch"
HorizontalAlignment="Left"
Margin="0,7,0,7"
Cursor="SizeWE"
Fill="Yellow"
Opacity="0"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
<Rectangle
Width="5"
VerticalAlignment="Stretch"
HorizontalAlignment="Right"
Margin="0,7,0,7"
Cursor="SizeWE"
Fill="Yellow"
Opacity="0"
Visibility="{Binding Visibility}"
MouseDown="Thumb_MouseDown"
MouseUp="Thumb_MouseUp"
MouseMove="Thumb_MouseMove"
/>
</Grid>
</DataTemplate>
</Grid.Resources>
<!--
This ListBox presents the data fields
The data template that defines the visuals for each data field is in the
resources section at the start of this file.
-->
<ListBox
x:Name="DataFieldListBox"
ItemsSource="{Binding GetDataFields}"
SelectionMode="Extended"
Background="Transparent"
>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem"
>
<Setter
Property="Canvas.Left"
Value="{Binding X}"
/>
<Setter
Property="Canvas.Top"
Value="{Binding Y}"
/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<!--
Render a drag selection rectangle using a Canvas with a border
-->
<Canvas
x:Name="dragSelectionCanvas"
Visibility="Collapsed"
>
<Border
x:Name="selectionRectangleBorder"
BorderBrush="Blue"
BorderThickness="1"
Background="LightBlue"
CornerRadius="1"
Opacity="0.5"
/>
</Canvas>
</Grid>
</Grid>
我确信我的代码充满了新手的错误,但到目前为止它很有趣。希望快速改进,并将其转化为有用的东西。欢迎提供反馈和见解。如果有人碰巧找到我出错的地方,你会感激不尽。
- 汤姆
答案 0 :(得分:1)
我怀疑它与您在XAML中定义Grid的DataContext
这一事实有关,但您的拖动事件正在引用代码隐藏中的对象。因此,ListBox
绑定到PageViewModel
的XAML副本,而您的代码隐藏正在处理PageViewModel
我建议从XAML中删除DataContext
属性,而不是在代码隐藏中设置它,例如Me.DataContext = Pages
请记住,除非是应用程序启动代码,否则在代码隐藏中设置DataContext
通常是不好的做法。