Domanda

Sto cercando di eseguire un Drag and drop Approach alla creazione di relazioni in un diagramma, direttamente analogo a SQL Server Management Studio Strumenti di diagramma. Ad esempio, nell'illustrazione seguente, l'utente trascinerebbe CustomerID dal User entità a Customer entità e crea una relazione chiave estera tra i due.

La funzione chiave desiderata è che un percorso ARC temporaneo verrebbe disegnato mentre l'utente esegue l'operazione di trascinamento, seguendo il mouse. Le entità o le relazioni in movimento una volta create non è il problema in cui mi sto imbattendo.

Entity–relationship diagram

Alcuni XAML di riferimento corrispondente a un'entità sul diagramma sopra:

<!-- Entity diagram control -->
<Grid MinWidth="10" MinHeight="10" Margin="2">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*" ></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid Grid.Row="0" Grid.Column="0" IsHitTestVisible="False" Background="{StaticResource ControlDarkBackgroundBrush}">
        <Label Grid.Row="0" Grid.Column="0" Style="{DynamicResource LabelDiagram}" Content="{Binding DiagramHeader, Mode=OneWay}" />
    </Grid>
    <ScrollViewer Grid.Row="1" Grid.Column="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Background="{StaticResource ControlBackgroundBrush}" >
        <StackPanel VerticalAlignment="Top">
            <uent:EntityDataPropertiesDiagramControl DataContext="{Binding EntityDataPropertiesFolder}" />
            <uent:CollectionEntityPropertiesDiagramControl DataContext="{Binding CollectionEntityPropertiesFolder}" />
            <uent:DerivedEntityDataPropertiesDiagramControl DataContext="{Binding DerivedEntityDataPropertiesFolder}" />
            <uent:ReferenceEntityPropertiesDiagramControl DataContext="{Binding ReferenceEntityPropertiesFolder}" />
            <uent:MethodsDiagramControl DataContext="{Binding MethodsFolder}" />
        </StackPanel>
    </ScrollViewer>
    <Grid Grid.RowSpan="2" Margin="-10">
        <lib:Connector x:Name="LeftConnector" Orientation="Left" VerticalAlignment="Center" HorizontalAlignment="Left" Visibility="Collapsed"/>
        <lib:Connector x:Name="TopConnector" Orientation="Top" VerticalAlignment="Top" HorizontalAlignment="Center" Visibility="Collapsed"/>
        <lib:Connector x:Name="RightConnector" Orientation="Right" VerticalAlignment="Center" HorizontalAlignment="Right" Visibility="Collapsed"/>
        <lib:Connector x:Name="BottomConnector" Orientation="Bottom" VerticalAlignment="Bottom" HorizontalAlignment="Center" Visibility="Collapsed"/>
    </Grid>
</Grid>

Il mio approccio attuale a farlo è:

1) Avvia l'operazione di trascinamento in un controllo figlio dell'entità, come ad esempio:

protected override void OnPreviewMouseMove(MouseEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed)
    {
        dragStartPoint = null;
    }
    else if (dragStartPoint.HasValue)
    {
        Point? currentPosition = new Point?(e.GetPosition(this));
        if (currentPosition.HasValue && (Math.Abs(currentPosition.Value.X - dragStartPoint.Value.X) > 10 || Math.Abs(currentPosition.Value.Y - dragStartPoint.Value.Y) > 10))
        {
            DragDrop.DoDragDrop(this, DataContext, DragDropEffects.Link);
            e.Handled = true;
        }
    }
}

2) Creare un dedornte del connettore quando l'operazione di trascinamento lascia l'entità, come ad esempio:

protected override void OnDragLeave(DragEventArgs e)
{
    base.OnDragLeave(e);
    if (ParentCanvas != null)
    {
        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(ParentCanvas);
        if (adornerLayer != null)
        {
            ConnectorAdorner adorner = new ConnectorAdorner(ParentCanvas, BestConnector);
            if (adorner != null)
            {
                adornerLayer.Add(adorner);
                e.Handled = true;
            }
        }
    }
}

3) Disegna il percorso ARC mentre il mouse viene spostato nel Connector Adorner, come: ad esempio:

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            if (!IsMouseCaptured) CaptureMouse();
            HitTesting(e.GetPosition(this));
            pathGeometry = GetPathGeometry(e.GetPosition(this));
            InvalidateVisual();
        }
        else
        {
            if (IsMouseCaptured) ReleaseMouseCapture();
        }
    }

Il diagramma Canvas è legato a un modello di vista e le entità e le relazioni su Canvas sono a loro volta legati ai rispettivi modelli di vista. Alcuni Xaml relativo al diagramma generale:

<ItemsControl ItemsSource="{Binding Items, Mode=OneWay}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <lib:DesignerCanvas VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Canvas.Left" Value="{Binding X}"/>
            <Setter Property="Canvas.Top" Value="{Binding Y}"/>
            <Setter Property="Canvas.Width" Value="{Binding Width}"/>
            <Setter Property="Canvas.Height" Value="{Binding Height}"/>
            <Setter Property="Canvas.ZIndex" Value="{Binding ZIndex}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

e DataTemplateS per gli entiti e le relazioni:

<!-- diagram relationship -->
<DataTemplate DataType="{x:Type dvm:DiagramRelationshipViewModel}">
    <lib:Connection />
</DataTemplate>
<!-- diagram entity -->
<DataTemplate DataType="{x:Type dvm:DiagramEntityViewModel}">
    <lib:DesignerItem>
        <lib:EntityDiagramControl />
    </lib:DesignerItem>
</DataTemplate>

Problema: Il problema è che una volta iniziata l'operazione di trascinamento, le mosse del mouse non vengono più monitorate e l'adorner del connettore non è in grado di disegnare l'arco come in altri contesti. Se rilascio il mouse e faccio di nuovo clic, l'arco inizia a disegnare, ma poi ho perso il mio oggetto sorgente. Sto cercando di capire un modo per passare l'oggetto sorgente insieme al movimento del topo.

Bounty: Ritorno a questo problema, attualmente ho intenzione di non utilizzare Drag and Lancia direttamente per farlo. Attualmente ho intenzione di aggiungere un drasinitem e isragging DependencyProperty Per il controllo del diagramma, che mantenga l'articolo da trascinare e flag se si verifica un'operazione di trascinamento. Potrei quindi usare DataTriggers per cambiare il Cursor e Adorner Visibilità basata su isragging e potrebbe utilizzare DragItem per l'operazione di caduta.

(Ma sto cercando di assegnare una taglia su un altro approccio interessante. Si prega di commentare se sono necessarie ulteriori informazioni o codice per chiarire questa domanda.)

Modificare: Priorità più bassa, ma sono ancora alla ricerca di una soluzione migliore per un approccio di diagramma di resistenza e drop. Vuoi implementare un approccio migliore in open source Mo+ Soluzione Builder.

È stato utile?

Soluzione 2

Come accennato in precedenza, il mio approccio attuale è di non utilizzare Drag and Drop direttamente, ma di usare una combinazione di DependencyProperties e gestire gli eventi del mouse per imitare una resistenza.

Il DependencyProperties Nel controllo del diagramma dei genitori sono:

public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register("IsDragging", typeof(bool), typeof(SolutionDiagramControl));
public bool IsDragging
{
    get
    {
        return (bool)GetValue(IsDraggingProperty);
    }
    set
    {
        SetValue(IsDraggingProperty, value);
    }
}

public static readonly DependencyProperty DragItemProperty = DependencyProperty.Register("DragItem", typeof(IWorkspaceViewModel), typeof(SolutionDiagramControl));
public IWorkspaceViewModel DragItem
{
    get
    {
        return (IWorkspaceViewModel)GetValue(DragItemProperty);
    }
    set
    {
        SetValue(DragItemProperty, value);
    }
}

Il IsDragging DependencyProperty viene utilizzato per innescare un cambio di cursore quando si svolge una resistenza, come: ad esempio:

<Style TargetType="{x:Type lib:SolutionDiagramControl}">
    <Style.Triggers>
        <Trigger Property="IsDragging" Value="True">
            <Setter Property="Cursor" Value="Pen" />
        </Trigger>
    </Style.Triggers>
</Style>

Ovunque io abbia bisogno di eseguire una forma di disegno ad arco di drag and drop, invece di chiamare DragDrop.DoDragDrop, Ho impostato IsDragging = true e DragItem all'elemento di origine che viene trascinato.

All'interno del controllo dell'entità sul congedo del mouse, è abilitato il connettore che disegna l'arco durante la resistenza, come: ad esempio:

protected override void OnMouseLeave(MouseEventArgs e)
{
    base.OnMouseLeave(e);
    if (ParentSolutionDiagramControl.DragItem != null)
    {
        CreateConnectorAdorner();
    }
}

Il controllo del diagramma deve gestire ulteriori eventi del mouse durante la resistenza, ad esempio:

protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    if (e.LeftButton != MouseButtonState.Pressed)
    {
        IsDragging = false;
        DragItem = null;
    }
}

Il controllo del diagramma deve anche gestire la "caduta" su un evento del mouse (e deve capire su quale entità viene eliminata in base alla posizione del mouse), come: ad esempio:

protected override void OnMouseUp(MouseButtonEventArgs e)
{
    base.OnMouseUp(e);
    if (DragItem != null)
    {
        Point currentPosition = MouseUtilities.GetMousePosition(this);
        DiagramEntityViewModel diagramEntityView = GetMouseOverEntity(currentPosition );
        if (diagramEntityView != null)
        {
            // Perform the drop operations
        }
    }
    IsDragging = false;
    DragItem = null;
}

Sto ancora cercando una soluzione migliore per disegnare l'arco temporaneo (seguendo il mouse) sul diagramma mentre si svolge un'operazione di resistenza.

Altri suggerimenti

Questa è una risposta abbastanza coinvolta. Fammi sapere se una parte di esso non è chiara.

Attualmente sto cercando di risolvere un problema simile. Nel mio caso, voglio associare la mia lista di elementi di elementi a una raccolta e quindi rappresentare ogni elemento in quella raccolta come a nodo cioè un oggetto trascinabile o un connessione cioè una linea tra i nodi che si ridisegna quando i nodi vengono trascinati. Ti mostrerò il mio codice e i dettagli in cui penso che potresti aver bisogno di apportare modifiche per soddisfare le tue esigenze.

Trascinando

Il trascinamento viene realizzato impostando proprietà allegate di proprietà di Dragger classe. Secondo me, questo ha un vantaggio nell'uso del MoveThumb Per eseguire il trascinamento che rendere un oggetto trascinabile non comporta la modifica del suo modello di controllo. La mia prima implementazione effettivamente utilizzata MoveThumb Nei modelli di controllo per ottenere il trascinamento, ma ho scoperto che ciò ha reso la mia applicazione molto fragile (l'aggiunta di nuove funzionalità spesso ha rotto il trascinamento). Ecco il codice per il dragger:

public static class Dragger
    {
        private static FrameworkElement currentlyDraggedElement;
        private static FrameworkElement CurrentlyDraggedElement
        {
            get { return currentlyDraggedElement; } 
            set
            {
                currentlyDraggedElement = value;
                if (CurrentlyDraggedElement != null)
                {
                    CurrentlyDraggedElement.MouseMove += new MouseEventHandler(CurrentlyDraggedElement_MouseMove);
                    CurrentlyDraggedElement.MouseLeftButtonUp +=new MouseButtonEventHandler(CurrentlyDraggedElement_MouseLeftButtonUp);
                }
            }           
        }

        private static ItemPreviewAdorner adornerForDraggedItem;
        private static ItemPreviewAdorner AdornerForDraggedItem
        {
            get { return adornerForDraggedItem; }
            set { adornerForDraggedItem = value; }
        }

        #region IsDraggable

        public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.RegisterAttached("IsDraggable", typeof(Boolean), typeof(Dragger),
            new FrameworkPropertyMetadata(IsDraggable_PropertyChanged));

        public static void SetIsDraggable(DependencyObject element, Boolean value)
        {
            element.SetValue(IsDraggableProperty, value);
        }
        public static Boolean GetIsDraggable(DependencyObject element)
        {
            return (Boolean)element.GetValue(IsDraggableProperty);
        }

        #endregion

        #region IsDraggingEvent

        public static readonly RoutedEvent IsDraggingEvent = EventManager.RegisterRoutedEvent("IsDragging", RoutingStrategy.Bubble,
            typeof(RoutedEventHandler), typeof(Dragger));

        public static event RoutedEventHandler IsDragging;

        public static void AddIsDraggingHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement uie = d as UIElement;
            if (uie != null)
            {
                uie.AddHandler(Dragger.IsDraggingEvent, handler);
            }
        }

        public static void RemoveIsDraggingEventHandler(DependencyObject d, RoutedEventHandler handler)
        {
            UIElement uie = d as UIElement;
            if (uie != null)
            {
                uie.RemoveHandler(Dragger.IsDraggingEvent, handler);
            }
        }

        #endregion

        public static void IsDraggable_PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if ((bool)args.NewValue == true)
            {
                FrameworkElement element = (FrameworkElement)obj;
                element.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(itemToBeDragged_MouseLeftButtonDown);
            }
        }

        private static void itemToBeDragged_MouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            var element = sender as FrameworkElement;
            if (element != null)
            {                
                CurrentlyDraggedElement = element;
            }           
        }

        private static void CurrentlyDraggedElement_MouseMove(object sender, MouseEventArgs e)
        {
            var element = sender as FrameworkElement;
            if (element.IsEnabled == true)
            {
                element.CaptureMouse();
                //RaiseIsDraggingEvent();
                DragObject(sender, new Point(Mouse.GetPosition(PavilionVisualTreeHelper.GetAncestor(element, typeof(CustomCanvas)) as CustomCanvas).X,
                    Mouse.GetPosition(PavilionVisualTreeHelper.GetAncestor(element, typeof(CustomCanvas)) as CustomCanvas).Y));
            }         
        }

        private static void CurrentlyDraggedElement_MouseLeftButtonUp(object sender, MouseEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            element.MouseMove -= new MouseEventHandler(CurrentlyDraggedElement_MouseMove);
            element.ReleaseMouseCapture();
            CurrentlyDraggedElement = null;
        }

        private static void DragObject(object sender, Point startingPoint)
        {
            FrameworkElement item = sender as FrameworkElement;

            if (item != null)
            {
                var canvas = PavilionVisualTreeHelper.GetAncestor(item, typeof(CustomCanvas)) as CustomCanvas;

                double horizontalPosition = Mouse.GetPosition(canvas).X - item.ActualWidth/2;
                double verticalPosition = Mouse.GetPosition(canvas).Y - item.ActualHeight/2;

                item.RenderTransform = ReturnTransFormGroup(horizontalPosition, verticalPosition);
                item.RaiseEvent(new IsDraggingRoutedEventArgs(item, new Point(horizontalPosition, verticalPosition), IsDraggingEvent));
            }
        }

        private static TransformGroup ReturnTransFormGroup(double mouseX, double mouseY)
        {
            TransformGroup transformGroup = new TransformGroup();
            transformGroup.Children.Add(new TranslateTransform(mouseX, mouseY));
            return transformGroup;
        }
    }

    public class IsDraggingRoutedEventArgs : RoutedEventArgs
    {
        public Point LocationDraggedTo { get; set;}
        public FrameworkElement ElementBeingDragged { get; set; }

        public IsDraggingRoutedEventArgs(DependencyObject elementBeingDragged, Point locationDraggedTo, RoutedEvent routedEvent)
            : base(routedEvent)
        {
            this.ElementBeingDragged = elementBeingDragged as FrameworkElement;
            LocationDraggedTo = locationDraggedTo;            
        }
    }

credo che Dragger richiede che l'oggetto sia su un file Canvas o CustomCanvas, ma non c'è una buona ragione, oltre alla pigrizia, per questo. È possibile modificarlo facilmente per funzionare per qualsiasi pannello. (È nel mio backlog!).

Il Dragger la classe sta anche usando il PavilionVisualTreeHelper.GetAncestor() Metodo helper, che si arrampica semplicemente l'albero visivo alla ricerca dell'elemento appropriato. Il codice per quello è di seguito.

 /// <summary>
    /// Gets ancestor of starting element
    /// </summary>
    /// <param name="parentType">Desired type of ancestor</param>
    public static DependencyObject GetAncestor(DependencyObject startingElement, Type parentType)
    {
        if (startingElement == null || startingElement.GetType() == parentType)
            return startingElement;
        else
            return GetAncestor(VisualTreeHelper.GetParent(startingElement), parentType);
    }

Consumare il Dragger La classe è molto semplice. Semplicemente impostato Dragger.IsDraggable = true Nel markup XAML del controllo appropriato. Facoltativamente, puoi registrarti al Dragger.IsDragging Evento, che bolle dall'elemento da trascinare, per eseguire qualsiasi elaborazione di cui potresti aver bisogno.

Aggiornamento della posizione di connessione

Il mio meccanismo per informare la connessione che deve essere ridisegnato è un po 'sciatto e ha sicuramente bisogno di readdressing.

La connessione contiene due dipendenze di tipo Frameworkelement: inizio e fine. Nei beni di proprietà, provo a lanciarli come DragADareListBoxItems (devo renderlo un'interfaccia per una migliore riusabilità). Se il cast ha esito positivo, mi registro al DragAwareListBoxItem.ConnectionDragging evento. (Cattivo nome, non mio!). Quando quell'evento spara, la connessione ridisegna il suo percorso.

Il DragADARELISTBoxItem in realtà non sa quando viene trascinato, quindi qualcuno deve dirlo. A causa della posizione di ListBoxItem nel mio albero visivo, non sente mai il Dragger.IsDragging evento. Quindi, per dirlo che viene trascinato, la casella di elenco ascolta l'evento e informa l'appropriato DragAWarelistBoxItem.

Stava per pubblicare il codice per il Connection, il DragAwareListBoxItem, e il ListBox_IsDragging, ma penso che sia troppo leggibile qui. Puoi controllare il progetto a http://code.google.com/p/pavilion/source/browse/#hg%2fpaviliondesignertool%2fpavilion.nodedesignero clonare il resoconto con clone Hg https://code.google.com/p/pavilion/ . È un progetto open source con la licenza MIT, quindi puoi adattarlo come ritieni opportuno. Come avvertimento, non esiste un rilascio stabile, quindi può cambiare in qualsiasi momento.

Connessibilità

Come per l'aggiornamento della connessione, non incollerò il codice. Invece, ti dirò quali classi nel progetto esaminare e cosa cercare in ogni classe.

Dal punto di vista dell'utente, ecco come funziona la creazione di una connessione. I clic destro del mouse su un nodo. Ciò fa apparire un menu contestuale da cui l'utente seleziona "Crea nuova connessione". Questa opzione crea una linea retta il cui punto di partenza è radicato sul nodo selezionato e il cui punto finale segue il mouse. Se l'utente fa clic su un altro nodo, viene creata una connessione tra i due. Se l'utente fa clic su qualsiasi altra parte, non viene creata alcuna connessione e la riga scompare.

In questo processo sono coinvolte due classi. Il ConnectionManager (che in realtà non gestisce alcuna connessione) Proprietà allegate. Il controllo di consumo imposta la proprietà ConnectionManager.isconnectable su True e imposta la proprietà ConnectionManager.MenuuiteEmInvoker sulla voce di menu che dovrebbe avviare il processo. Inoltre, un certo controllo nel tuo albero visivo deve ascoltare l'evento routing di connessione. È qui che avviene l'effettiva creazione della connessione.

Quando viene selezionata la voce di menu, il ConnectionManager crea un lineadorner. ConnectionManager ascolta l'evento LineAdorner LeftClick. Quando quell'evento viene licenziato, eseguo test di successo per trovare il controllo selezionato. Poi sollevo l'evento di connessione, passando nell'evento args i due controlli che voglio creare la connessione tra. Spetta all'abbonato dell'evento fare effettivamente il lavoro.

Penso che vorrai guardare nel controllo del pollice WPF. Avvolge alcune di queste funzionalità in un pacchetto conveniente.

Ecco la documentazione MSDN:

http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.thumb.aspx

Ecco un esempio:

http://denisvuyka.wordpress.com/2007/10/13/wpf-draggable-objets-and-simple-shape-connectors/

Sfortunatamente non ho molta esperienza in questo settore, ma penso che questo sia quello che stai cercando. Buona fortuna!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top