#vb.net #xaml #mvvm #uwp #windows-10-universal
#vb.net #xaml #mvvm #uwp #windows-10-универсальный
Вопрос:
В настоящее время я создаю свое приложение для Windows 10, и я пытаюсь переместить прямоугольник на холсте, следуя шаблону mvvm. Приведенный ниже код работает, но я сломал mvvm, используя uielemnt в моем viewmodel PointerDragEvent.
Dim rec = TryCast(e.OriginalSource, Button)
Dim selrecitem = TryCast(rec.DataContext, RectItem)
Вопрос
Есть ли не хакерский / правильный способ сделать это?
Как я могу получить элемент, на который я нажимаю, и передать его в мою viewmodel?
Все элементы на холсте будут создаваться динамически.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App11"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
x:Class="App11.MainPage"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.DataContext>
<local:myviewmodel/>
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="8*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="11*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl x:Name="itemsControl" Grid.Column="1" Grid.Row="1" ItemsSource="{Binding myrectangles, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ManipulationDelta">
<Core:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="PointerDrag"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="PageItem" Background="Transparent" BorderBrush="DodgerBlue" Width="{Binding Width}" Height="{Binding Height}" ManipulationMode="TranslateX, TranslateY" IsHitTestVisible="{Binding IsChecked, ElementName=SelectToolButton, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" Command="{Binding SendMyDC, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<Button.RenderTransform>
<CompositeTransform TranslateX="{Binding X}" TranslateY="{Binding Y}"/>
</Button.RenderTransform>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Viewmodel
Public Class myviewmodel
Implements INotifyPropertyChanged
Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _myrectangles As New ObservableCollection(Of RectItem)
Public Property myrectangles As ObservableCollection(Of RectItem)
Get
Return _myrectangles
End Get
Set(value As ObservableCollection(Of RectItem))
_myrectangles = value
NotifyPropertyChanged()
End Set
End Property
Public Sub New()
Dim newrect As New RectItem
newrect.Height = 100
newrect.Width = 150
newrect.X = 50
newrect.Y = 50
_myrectangles.Add(newrect)
End Sub
Public Sub PointerDrag(sender As Object, e As ManipulationDeltaRoutedEventArgs)
Dim dx_point = e.Delta.Translation.X
NotifyPropertyChanged()
Dim dy_point = e.Delta.Translation.Y
NotifyPropertyChanged()
Dim rec = TryCast(e.OriginalSource, Button)
Dim selrecitem = TryCast(rec.DataContext, RectItem)
selrecitem.X = dx_point
selrecitem.Y = dy_point
End Sub
End Class
RectItemClass
Public Class RectItem
Implements INotifyPropertyChanged
Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property X As Double
Get
Return m_X
End Get
Set
m_X = Value
NotifyPropertyChanged()
End Set
End Property
Private m_X As Double
Public Property Y As Double
Get
Return m_Y
End Get
Set
m_Y = Value
NotifyPropertyChanged()
End Set
End Property
Private m_Y As Double
Public Property Width As Double
Get
Return m_Width
End Get
Set
m_Width = Value
NotifyPropertyChanged()
End Set
End Property
Private m_Width As Double
Public Property Height As Double
Get
Return m_Height
End Get
Set
m_Height = Value
NotifyPropertyChanged()
End Set
End Property
Private m_Height As Double
End Class
Редактировать #1 Вместо использования элемента управления item я использовал ListView / Listbox и привязал выбранный элемент к свойству в моей vewimodel. В listview возникает некоторая проблема с выравниванием презентатора элемента с элементом, который он представляет, и с помощью listbox событие pointer up не срабатывает.
<ListView x:Name="itemsControl" Grid.Column="1" Grid.Row="1" ItemsSource="{Binding myrectangles, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Background="#FFF2F2F2" SelectedItem="{Binding selectedrec, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ManipulationDelta">
<Core:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="PointerDrag"/>
</Core:EventTriggerBehavior>
<Core:EventTriggerBehavior EventName="PointerPressed">
<Core:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="PointerPressed"/>
</Core:EventTriggerBehavior>
<!--<Core:EventTriggerBehavior EventName="PointerReleased">
<Core:CallMethodAction TargetObject="{Binding Mode=OneWay}" MethodName="Up"/>
</Core:EventTriggerBehavior>-->
</Interactivity:Interaction.Behaviors>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="White">
</Canvas>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<!--<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>-->
<ListView.ItemTemplate>
<DataTemplate>
<Rectangle x:Name="PageItem" Width="{Binding Width}" Height="{Binding Height}" Fill="Transparent" Stroke="DodgerBlue" ManipulationMode="TranslateX, TranslateY" IsHitTestVisible="{Binding IsChecked, ElementName=SelectToolButton, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" >
<Rectangle.RenderTransform>
<CompositeTransform TranslateX="{Binding X}" TranslateY="{Binding Y}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Обновлена ViewModel
Public _selectedrec As New RectItem
Public Property selectedrec As RectItem
Get
Return _selectedrec
End Get
Set(value As RectItem)
_selectedrec = value
NotifyPropertyChanged()
End Set
End Property
Public Sub PointerDrag(sender As Object, e As ManipulationDeltaRoutedEventArgs)
Dim dx_point = e.Delta.Translation.X
NotifyPropertyChanged()
Dim dy_point = e.Delta.Translation.Y
NotifyPropertyChanged()
selectedrec.X = dx_point
NotifyPropertyChanged()
selectedrec.Y = dy_point
NotifyPropertyChanged()
End Sub
Пример проблем
Нет itemcontainerstyle — При попытке перетащить привязки объекта на 0,0 холста
MyHalfFix — Раскомментируйте ItemContainerStyle — все работает правильно, к чему я стремлюсь
Общая проблема при реализации в реальной программе
Красный элемент — это listviewitem presenter . Он рисуется правильно, но его расположение не выровнено с нарисованным прямоугольником, он всегда привязывается к точке 0,0 холста. Я могу сделать его прозрачным, но, как вы также можете видеть при рисовании прямоугольника, он неправильно следует за курсором
Ответ №1:
Вы можете использовать InvokeCommandAction для ManipulationDelta
события, чтобы избежать нарушения шаблона MVVM. InvokeCommandAction
можно передать параметр в viewmodel
, и мы можем передать selecteditem
по параметру. Объектом ManipulationDelta
должна быть кнопка вместо ItemsControl
и InvokeCommandAction
будет работать, то, что вы на самом деле перетаскиваете, — это сама кнопка. Обновленный код следующим образом:
XAML
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="root">
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="PageItem" Background="Transparent" BorderBrush="DodgerBlue" Width="{Binding Width}" Height="{Binding Height}" ManipulationMode="TranslateX, TranslateY" IsHitTestVisible="{Binding IsChecked, ElementName=SelectToolButton, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" >
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ManipulationDelta" SourceObject="{Binding ElementName=PageItem}">
<Core:InvokeCommandAction Command="{Binding DataContext.SendMyDC, ElementName=root}" CommandParameter="{Binding}"></Core:InvokeCommandAction>
<Core:CallMethodAction TargetObject="{Binding DataContext, ElementName=root, Mode=OneWay}" MethodName="PointerDrag" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<Button.RenderTransform>
<CompositeTransform TranslateX="{Binding X}" TranslateY="{Binding Y}"/>
</Button.RenderTransform>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
myviewmodel
Public Class myviewmodel
...
Private m_sendCommand As IDelegateCommand
Public Property SendMyDC As IDelegateCommand
Get
Return m_sendCommand
End Get
Protected Set(value As IDelegateCommand)
m_sendCommand = value
End Set
End Property
Public Sub New()
Me.SendMyDC = New DelegateCommand(AddressOf ExecuteSendMyDC)
Dim newrect As New RectItem
newrect.Height = 100
newrect.Width = 150
newrect.X = 50
newrect.Y = 50
_myrectangles.Add(newrect)
End Sub
Dim selrecitem As RectItem
Public Sub PointerDrag(sender As Object, e As ManipulationDeltaRoutedEventArgs)
Dim dx_point = e.Delta.Translation.X
NotifyPropertyChanged()
Dim dy_point = e.Delta.Translation.Y
NotifyPropertyChanged()
'Dim rec = TryCast(e.OriginalSource, Button)
'Dim selrecitem = TryCast(rec.DataContext, RectItem)
If selrecitem IsNot Nothing Then
selrecitem.X = dx_point
selrecitem.Y = dy_point
End If
End Sub
Private Sub ExecuteSendMyDC(param As Object)
selrecitem = CType(param, RectItem)
End Sub
End Class
Класс DelegateCommand
Public Class DelegateCommand
Implements IDelegateCommand
Private _execute As Action(Of Object)
Private _canExecute As Func(Of Object, Boolean)
#Region "Constructors"
Public Sub New(execute As Action(Of Object), canExecute As Func(Of Object, Boolean))
Me._execute = execute
Me._canExecute = canExecute
End Sub
Public Sub New(execute As Action(Of Object))
Me._execute = execute
Me._canExecute = AddressOf Me.AlwaysCanExecute
End Sub
#End Region
#Region "IDelegateCommand"
Private Function AlwaysCanExecute(param As Object) As Boolean
Return True
End Function
Public Function CanExecute(parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
Return _canExecute(parameter)
End Function
Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged
Public Sub Execute(parameter As Object) Implements System.Windows.Input.ICommand.Execute
_execute(parameter)
End Sub
Public Sub RaiseCanExecuteChanged() Implements IDelegateCommand.RaiseCanExecuteChanged
RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
End Sub
#End Region
End Class
Public Interface IDelegateCommand
Inherits ICommand
Sub RaiseCanExecuteChanged()
End Interface
DelegateCommand — это обычный класс для дальнейшего вызова других команд. Более подробную информацию см. в привязке команды внутри DataTemplate.
Комментарии:
1. Я попытался реализовать предоставленный вами код, но он не сработал. Ничего не возникает, когда я начинаю перетаскивать. Я нашел обходной путь, хотя я обновлю вопрос
2. @Twinnaz Я загрузил завершенную демонстрационную версию, которую я хорошо протестировал, на github , на которую вы можете ссылаться, если все еще есть проблемы, пожалуйста, не стесняйтесь, дайте мне знать.
3. Это хорошо работает в примере, который я предоставил, спасибо. К сожалению, это не работает во всей программе, в которой я пытаюсь это реализовать. Кнопка будет отображаться динамически. Я могу предоставить код, но я думаю, что его слишком много для публикации здесь.
4. @Twinnaz если удобно, не могли бы вы загрузить на github или onedrive или где-нибудь еще. Вы знаете, мне трудно догадаться, что в этом плохого без кода. В противном случае я могу только догадываться.
5. @Twinnaz Я протестировал его на своей стороне и могу воспроизвести. Это совершенно новая проблема стиля. Список имеет свой собственный макет, то, что вы на самом деле перетащили, — это внутренний элемент управления, который вы определили в шаблоне элемента, а не сам элемент списка. Почему вы использовали listbox? Поскольку работа вокруг вас найдена, а затем добавление полного цвета с прямоугольником будет соответствовать вашим требованиям, верно?