0

I am new to MVVM and I am currently trying to add the drag/drop feature to my application. The thing is I already developed the interface in the code-behind but I am trying now to re-write the code into MVVM as I am only at the beginning of the project.

Here is the context: the user will be able to add boxes (ToggleButton but it may change) to a grid, a bit like a chessboard. Below is the View Model I am working on:

<Page.Resources>
    <Style TargetType="{x:Type local:AirportEditionPage}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Page}">

                    <!-- The page content-->
                    <Grid>

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="{Binding ToolKitWidth, FallbackValue=50}" />
                            <ColumnDefinition Width="*"/>
                            <ColumnDefinition Width="{Binding RightPanelWidth, FallbackValue=400}"/>
                        </Grid.ColumnDefinitions>

                        <!-- The airport grid where Steps and Links are displayed -->
                        <ScrollViewer Grid.ColumnSpan="4" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                            <Viewbox Height="{Binding AirportGridHeight}" Width="{Binding AirportGridWidth}" RenderOptions.BitmapScalingMode="HighQuality">
                                <ItemsControl x:Name="ChessBoard" ItemsSource="{Binding Items}">
                                    <ItemsControl.ItemsPanel>
                                        <ItemsPanelTemplate>
                                            <Canvas Width="{Binding CardQuantityRow}" Height="{Binding CardQuantityColumn}" Background="{StaticResource AirportGridBackground}"/>
                                        </ItemsPanelTemplate>
                                    </ItemsControl.ItemsPanel>
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <Grid Width="1" Height="1">
                                                <ToggleButton Style="{StaticResource StepCardContentStyle}"/>
                                            </Grid>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                    <ItemsControl.ItemContainerStyle>
                                        <Style>
                                            <Setter Property="Canvas.Left" Value="{Binding Pos.X}"/>
                                            <Setter Property="Canvas.Top" Value="{Binding Pos.Y}" />
                                        </Style>
                                    </ItemsControl.ItemContainerStyle>
                                </ItemsControl>
                            </Viewbox>
                        </ScrollViewer>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>

Items are basically from a class (child of INotifiedPropertyChanged) with a name, an icon and a position (Point).

Now, I am trying to make the user able to drag and drop the box (ToggleButton) within the grid wherever he/she wants. However, I am totally lost with Commands, AttachedProperties etc. I spent all the whole day on tutorials and tried drag/drop solutions but with my poor knowledge, I don't know how to apply all of this into my code.

On my code-behinded version of the code, it was easy. When the button is left-clicked, I say to a variable of the grid "hey, I am being dragged and dropped". While the user is moving, I changed the Item coordinates and when the user released the left button (left button up), the dragdrop_object variable comes null again.

In the frame of the MVVM, I am totally lost. Could you give me some tracks to help me trough ? I intended to give up with MVVM a lot of time, but I know that it is better to keep up even if every little feature takes litteraly hours for me to implement (it should decrease with time...).

Do not hesitate if you need further details to answer to my question.

Jason
  • 9
  • 6
  • MVVM basically means separation of view from model and connect them via view-model. This drag-drop looks like a view thing which you mentioned you managed by code-behind of the XAML files and that's MVVM. If the drag-and-drop is supposed to affect the model, the view (XAML+code behind) have access to view-model. So, get the view-model in the code-behind and impose changes. – Sorush Jul 19 '20 at 21:28
  • Thanks for your reply Sorush ! So If I understand correctly, making it in the code-behind is not against MVVM, even if it interacts with my model (here my Items). That's not easy when we begin in the MVVM world to make distinction between what can be implemented in the code-behind and what cannot. Moreover, I read articles about making drag/drop in ViewModel so it could have get me quite confused. – Jason Jul 19 '20 at 21:52
  • Yes, XAML and code-behind are both the view. Some stuff is easier with code-behind like a complex view logic, and some easier with XAML like binding a view element to a view-model property. I put emphasis on that the view knows only the view-model (not the model). View-model only knows the model. The model knows nothing of the others. Basically, the model is a portable code, you can use it somewhere else like in a web application. For distinction, write your app in a console application that does all the logic, create a view that a user expected to see, and connect them with a view-model. – Sorush Jul 19 '20 at 22:50
  • Thanks again for all these details. It helps me a lot. The thing is I already worked on a huge algorithm in a console app. I am indeed know building the interface before connecting the two apps and I am a bit lost but I am starting to see clearer. Initially, I had also created the interface (a big part of it) using full code-behind (0 binding) where every works well but I found out MVVM was a better Architecture. I am now trying to migrating the code towards this architecture but with pain. I will do my best and keep in mind your precious advices. – Jason Jul 19 '20 at 23:02
  • Generally MVVM is not about code-behind, but separation of business logic and view. There is nothing wrong in implementing certain logic in C# (or code-behind). Handling UI events is one scenario that requires some C#, when they can't be handled using `EventTrigger`. [Patterns - WPF Apps With The Model-View-ViewModel Design Pattern](https://learn.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern). You don't have to wrap the `ItemsControl` into a `ViewBox`. – BionicCode Jul 20 '20 at 08:46
  • Also the `ScrollViewer` is not necessary. Better use a `ListBox` which has the `ScrolViewer` already onboard. You are binding to UI related layout data like `ToolKitWidth` or `AirportGridHeight`. The binding source is identical with the data source for the `ItemsControl`. This means you have either implemented these properties in the view model, which would generally violate MVVM, since this is view related data, or the `Items`collection is implemented in your view, which would show that you are not using MVVM and your understanding of the view model is wrong. – BionicCode Jul 20 '20 at 08:46
  • I only can assume as this details like your binding source are not shown. – BionicCode Jul 20 '20 at 08:47
  • Hello BionicCode. Thanks for all these details, I'll read your link asap. My View code comes from the following link: [link](https://stackoverflow.com/questions/20560519/wpf-controls-needed-to-build-chess-application/20563181), where I adapted it to my context. I thought ListBox where more used with textboxs/textblocks, I didn't think using it with canvas. I will try it. Thanks. – Jason Jul 20 '20 at 10:29
  • The items source is for now implemented in the ModelView for test only, but it should further be in the model. "I only can assume as this details like your binding source are not shown." Of course, no worry, if you think I need to edit my initial post to add these information, I can do it :) – Jason Jul 20 '20 at 10:37
  • @Sorush, I would like to, but I do not see any way to vote-up your very helpful comments, I only see a red flag (but that's not what we want here). How can I vote-up commentaries as they are not considered as answers? – Jason Jul 20 '20 at 11:45

1 Answers1

0

I found the solution here : Move items in a canvas using MVVM and here : Combining ItemsControl with draggable items - Element.parent always null

To be precise, here is the code I added :

public class DragBehavior
{
    public readonly TranslateTransform Transform = new TranslateTransform();
    private static DragBehavior _instance = new DragBehavior();
    public static DragBehavior Instance
    {
        get { return _instance; }
        set { _instance = value; }
    }

    public static bool GetDrag(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsDragProperty);
    }

    public static void SetDrag(DependencyObject obj, bool value)
    {
        obj.SetValue(IsDragProperty, value);
    }

    public static readonly DependencyProperty IsDragProperty =
      DependencyProperty.RegisterAttached("Drag",
      typeof(bool), typeof(DragBehavior),
      new PropertyMetadata(false, OnDragChanged));

    private static void OnDragChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        // ignoring error checking
        var element = (UIElement)sender;
        var isDrag = (bool)(e.NewValue);

        Instance = new DragBehavior();
        ((UIElement)sender).RenderTransform = Instance.Transform;

        if (isDrag)
        {
            element.MouseLeftButtonDown += Instance.ElementOnMouseLeftButtonDown;
            element.MouseLeftButtonUp += Instance.ElementOnMouseLeftButtonUp;
            element.MouseMove += Instance.ElementOnMouseMove;
        }
        else
        {
            element.MouseLeftButtonDown -= Instance.ElementOnMouseLeftButtonDown;
            element.MouseLeftButtonUp -= Instance.ElementOnMouseLeftButtonUp;
            element.MouseMove -= Instance.ElementOnMouseMove;
        }
    }

    private void ElementOnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        ((UIElement)sender).CaptureMouse();
    }

    private void ElementOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
    {
        ((UIElement)sender).ReleaseMouseCapture();
    }

    private void ElementOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
        FrameworkElement element = sender as FrameworkElement;
        Canvas parent = element.FindAncestor<Canvas>();
        var mousePos = mouseEventArgs.GetPosition(parent);
        if (!((UIElement)sender).IsMouseCaptured) return;
        if (mousePos.X < parent.Width && mousePos.Y < parent.Height && mousePos.X >= 0 && mousePos.Y >=0)
            ((sender as FrameworkElement).DataContext as Step).Pos = new System.Drawing.Point(Convert.ToInt32(Math.Floor(mousePos.X)), Convert.ToInt32((Math.Floor(mousePos.Y))));
    }


}

And my DataTemplate is now:

<DataTemplate>
     <ContentControl Height="1" Width="1" local:DragBehavior.Drag="True" Style="{StaticResource StepCardContentControl}"/>
</DataTemplate>

I added the FindAncestor static class in a dedicated file like this:

public static class FindAncestorHelper
{
    public static T FindAncestor<T>(this DependencyObject obj)
    where T : DependencyObject
    {
        DependencyObject tmp = VisualTreeHelper.GetParent(obj);
        while (tmp != null && !(tmp is T))
        {
            tmp = VisualTreeHelper.GetParent(tmp);
        }
        return tmp as T;
    }
}

(My items are now ContentControls). As the items' positions within the canvas are directly managed with their Pos variable (Canvas.SetLeft and Canvas.SetTop based on Pos (Pos.X and Pos.Y) with Binding), I just update it according to the MousePosition within the Canvas.

Also, as suggested in a commentary, I will see if there is something better than the ScrollViewer and Viewbox I'm using.

Jason
  • 9
  • 6