Search code examples
c#.netwpfxamluser-controls

c#/WPF product configurator/ draggable items


I'm currently building a product configurator in C#/WPF. The Product is something like a custom outlet strip. It's made out of an aluminum profile with variable length. You can choose the outlets and place them along the aluminum profile.

We already have the data model. We have the class "profile" with properties like "length" and the class "items" with properties like "position", "type" etc.

I made a funktion to visualise the data. I have a MainCanvas where im drawing the profile as rectangle and for the witdth I'm using the property "width". The items that belong to that product are in a list. I'm drawing the items with using a for-loop to insert a wpf for each plug in the list "items".

When I'm changing the data, I have to clear the canvas and redraw. I think the next step will be binding the variables of the class to the WPF properties, for example the position of a plug or the lenth of the profile.

Later, the plugs should be select/dragable to change their position (in a grid of 5mm , for example). That's the pint where I'm stuck. I know I can select and drag the plugs with actions like "OnLeftMouseButton". The question now is: In what type of container should I put the plugs in?

I thought I could wrap the WPF-Code of a plug inside a UserControl. Is that the right approach for that? As far as I know, I can make a UserControl selectable with Hittest. The Hittestresult will be put in a list selected. For displaying the selection I could use the Borderthickness/Brush of the UserControl. For dragging, I could change the Position with a ManupilationDelta (and change the binded Position-Variable). Because the count of the plugs is variable, I have to generate the UserControls from C#-Code. I know that Hittest is not easy to implement working with UserControls, because they are not really "visible".

I'm kind of new to C# and I have a hard time finding someone on the internet with similar problems or projects, maybe because I'm searching for the wrong words. Are my assumptions correct? What WPF controls would you use for that?


Solution

  • I used this answer and add few modifications.

    I used an ItemControl to create the view containing your different objects. That way, you can add images just by adding object to a list. The container is a Canvas, but it can be anything since the position is controlled by RenderTransform :

    <ItemsControl Name="MainView" ItemsSource="{Binding ListObjects}">
        <ItemsControl.ItemsPanel >
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="{x:Type local:MVDragableObject}">
                <local:DragableObject/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Here is the code behind. Some basic ModelView:

    public class MVObjectManager
    {
        public ObservableCollection<MVDragableObject> ListObjects { get; set; }
        public MVObjectManager()
        {
            ListObjects = new ObservableCollection<MVDragableObject>();
        }
    }
    public class MVDragableObject
    {
    }
    

    And the code to fill and bind the container. You can notice that I added 3 items to the collection:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            MVObjectManager Manager=new MVObjectManager();
            Manager.ListObjects.Add(new MVDragableObject());
            Manager.ListObjects.Add(new MVDragableObject());
            Manager.ListObjects.Add(new MVDragableObject());
            MainView.DataContext  = Manager;
        }
    }
    

    I defined a very simple UserControl. It is up to you to customize it:

    <UserControl x:Class="StackFill.DragableObject"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d">
    <Grid>
        <Rectangle Fill="Red" Height="30" Width="30"/>
    </Grid>
    
    </UserControl>
    

    And here is the code behind that manage the Drag and Drop behavior:

    public partial class DragableObject : UserControl
    {
        public DragableObject()
        {
            InitializeComponent();
            this.MouseLeftButtonDown += new MouseButtonEventHandler(DragableObject_MouseLeftButtonDown);
            this.MouseLeftButtonUp += new MouseButtonEventHandler(DragableObject_MouseLeftButtonUp);
            this.MouseMove += new MouseEventHandler(DragableObject_MouseMove);
        }
    
        protected bool isDragging;
        private Point clickPosition;
    
        private void DragableObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            isDragging = true;
            var draggableControl = sender as UserControl;
            clickPosition = e.GetPosition(this.Parent as UIElement);
            draggableControl.CaptureMouse();
        }
    
        private void DragableObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            isDragging = false;
            var draggable = sender as UserControl;
            draggable.ReleaseMouseCapture();
        }
    
        private void DragableObject_MouseMove(object sender, MouseEventArgs e)
        {
            var draggableControl = sender as UserControl;
    
            if (isDragging && draggableControl != null)
            {
                Point currentPosition = e.GetPosition(this.Parent as UIElement);
    
                var transform = draggableControl.RenderTransform as TranslateTransform;
                if (transform == null)
                {
                    transform = new TranslateTransform();
                    draggableControl.RenderTransform = transform;
                }
    
                transform.X = snapPosition(currentPosition.X - clickPosition.X, 10);
                transform.Y = snapPosition(currentPosition.Y - clickPosition.Y, 10);
            }
        }
    
        private double snapPosition(double position, double gridSize)
        {
            return (Math.Truncate(position / gridSize) * gridSize);
        }
    }
    

    You can control the snap precision by changing the gridSize argument.