Twinnaz Twinnaz - 1 month ago 21
Vb.net Question

Getting selected ItemsControl Item

Currentlly creating my windows 10 App, and I'm trying to move a rectangle on canvas following mvvm pattern. The code below works but I broke mvvm by using a uielemnt in my viewmodel PointerDragEvent.

Dim rec = TryCast(e.OriginalSource, Button)
Dim selrecitem = TryCast(rec.DataContext, RectItem)


Question

Is there a non hacky/proper way of doing this?

How can I retrieve the item I click on and pass it to my viewmodel?

All items on canvas will be dynamically created.

<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


Edit #1 Instead of using an item control I used an ListView/Listbox and binded the selected item to a property in my vewimodel. With the listview there is some problem getting the item presenter aligned with the item its's presenting and with listbox, the pointer up event doesn't fire.

<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>


Updated 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


Example of problems

No itemcontainerstyle - When trying to drag object snaps to 0,0 of canvas

enter image description here

MyHalfFix - Uncomment the ItemContainerStyle - Everything works correctly what im aiming for

enter image description here

The Overall Problem when implemented in the actual program

enter image description here

The red Item is the listviewitem presenter. It's being drawn correctly but the positioning is not aligned with the drawn rectangle it always snaps to point 0,0 of canvas. I can make it transparent but as you can also see when drawing the rectangle it doesn't follow the cursor correctly

Answer

You can use a InvokeCommandAction for the ManipulationDelta event will avoid breaking the MVVM pattern. InvokeCommandAction can pass a parameter to the viewmodel and we can pass the selecteditem by the parameter. The object of ManipulationDelta should be the button instead of the ItemsControl and the InvokeCommandAction will work, what you actually drag is the button itself. Updated code as follows:

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 class

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 is a common class for you further invoking other commands. More details please reference Command binding inside DataTemplate.

Comments