Existe-t-il un moyen de créer une copie du canevas avec le nouveau canevas sous la forme d'une boîte d'éléments enfants du premier canevas?

first canvas

Première toile

second canvas

Deuxième toile

Je veux quelque chose comme ça que je montre dans la deuxième toile.

0
gmetax 5 janv. 2016 à 03:01

2 réponses

Meilleure réponse

J'ai une réponse avec des lignes en tant qu'enfants, mais je veux une meilleure solution.

Point firstPoint = new Point(DrawCanvas.ActualWidth, DrawCanvas.ActualHeight);
Point endPoint = new Point(0, 0);
foreach (Line element in DrawCanvas.Children)
{

    double x = element.X1;
    double y = element.Y1;


    if (x < firstPoint.X)
        firstPoint.X = x;

    if (y < firstPoint.Y)
        firstPoint.Y = y;

    if (element.X2 > endPoint.X)
        endPoint.X = element.X2;

    if (element.Y2 > endPoint.Y)
        endPoint.Y = element.Y2;
}

double offsetX = firstPoint.X - 5;
double offsetY = firstPoint.Y - 5;


var children = DrawCanvas.Children.Cast<Line>().ToArray();
DrawCanvas.Children.Clear();

Rectangle rect = new Rectangle();
rect.Stroke = new SolidColorBrush(Color.FromRgb(0, 111, 0));
rect.Fill = new SolidColorBrush(Color.FromRgb(0, 111, 111));
rect.Width = endPoint.X - offsetX + 5;
rect.Height = endPoint.Y - offsetY + 5;
Canvas.SetLeft(rect, 0);
Canvas.SetTop(rect, 0);

newCanvas.Children.Add(rect);
newCanvas.Width = rect.Width;
newCanvas.Height = rect.Height;

foreach (Line element in children)
{
    Line newLine = element;
    newLine.X1 = element.X1 - offsetX;
    newLine.X2 = element.X2 - offsetX;
    newLine.Y1 = element.Y1 - offsetY;
    newLine.Y2 = element.Y2 - offsetY;

    newCanvas.Children.Add(newLine);
}
0
gmetax 5 janv. 2016 à 13:26

Vous pouvez le faire en dérivant de Canvas et en remplaçant OnVisualChildrenChanged.

Vous devez rendre les modifications publiques afin que votre Canvas copié (appelons-le la cible Canvas) puisse être correctement mis à jour.

Ce que vous voulez faire est assez complexe si vous devez écouter les modifications lorsque le canevas source a des enfants dont les propriétés sont mises à jour. Dans ce cas, vous devez à nouveau informer votre canevas cible des modifications, ce qui peut être effectué en ajoutant des liaisons.

XAML

<Window x:Class="StackOverflow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:StackOverflow"
    Title="MainWindow" Height="437.042" Width="525">
    <Grid>
        <Border Height="213" Margin="10,10,10,0" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="1">
            <local:ObservableCanvas x:Name="CanvasSource"
                VisualChildrenChanged="CanvasSource_VisualChildrenChanged">

                <!-- Add some elements -->
                <Ellipse Width="10" Height="10" Fill="Red"/>
                <Ellipse Canvas.Left="10" Canvas.Top="10"
                         Width="20" Height="20" Fill="Green"/>
                <Ellipse Canvas.Left="30" Canvas.Top="30"
                         Width="30" Height="30" Fill="Blue"/>
                <Ellipse Canvas.Left="60" Canvas.Top="60"
                         Width="40" Height="40" Fill="Yellow"/>
            </local:ObservableCanvas>
        </Border>

        <Border Margin="148,228,148,10" BorderBrush="Black" BorderThickness="1">
            <Canvas x:Name="CanvasTarget" Loaded="CanvasTarget_Loaded"/>
        </Border>
    </Grid>
</Window>

Au moment du design, voici la fenêtre: Window - Design Time

En particulier, notez le VisualChildrenChanged="CanvasSource_VisualChildrenChanged". C'est ainsi que les modifications sont rendues publiques. Nous gérons ces changements dans le code-behind pour le MainWindow.

Toile observable

//This class notifies listeners when child elements are added/removed & changed.
public class ObservableCanvas : Canvas, INotifyPropertyChanged
{
    //Implement INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;

    //This function should be called when a child element has a property updated.
    protected virtual void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    //Create a routed event to indicate when the ObservableCanvas has changes to its child collection.
    public static readonly RoutedEvent VisualChildrenChangedEvent = EventManager.RegisterRoutedEvent(
        "VisualChildrenChanged",
        RoutingStrategy.Bubble,
        typeof(RoutedEventHandler),
        typeof(ObservableCanvas));

    //Create CLR event handler.
    public event RoutedEventHandler VisualChildrenChanged
    {
        add { AddHandler(VisualChildrenChangedEvent, value); }
        remove { RemoveHandler(VisualChildrenChangedEvent, value); }
    }

    //This function should be called to notify listeners
    //to changes to the child collection.
    protected virtual void RaiseVisualChildrenChanged()
    {
        RaiseEvent(new RoutedEventArgs(VisualChildrenChangedEvent));
    }

    //Override to make the changes public.
    protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
    {
        base.OnVisualChildrenChanged(visualAdded, visualRemoved);

        //Create bindings here to properties you need to monitor for changes.
        //This example shows how to listen for changes to the Fill property.
        //You may need to add more bindings depending on your needs.
        Binding binding = new Binding("Fill");
        binding.Source = visualAdded;
        binding.NotifyOnTargetUpdated = true;
        binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        SetBinding(Shape.FillProperty, binding);

        //Instruct binding target updates to cause a global property
        //update for the ObservableCanvas.
        //This will make child property changes visible to the outside world.
        Binding.AddTargetUpdatedHandler(this,
            new EventHandler<DataTransferEventArgs>((object sender, DataTransferEventArgs e) =>
        {
            RaisePropertyChanged("Fill");
        }));

        //Notify listeners that the ObservableCanvas had an item added/removed.
        RaiseVisualChildrenChanged();
    }
}

Comme vu ci-dessus, cette classe gère principalement l'ajout / la suppression d'éléments enfants vers / depuis le ObservableCanvas. Vous devrez peut-être conserver une liste globale des liaisons pour les éléments ajoutés, et vous devrez peut-être envisager de détruire les liaisons lorsque des éléments sont supprimés.

Code derrière

Enfin, nous devons gérer les notifications publiées publiquement afin que la cible Canvas puisse être mise à jour. Pour simplifier, nous faisons cela dans le code-behind du MainWindow.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        //Demonstrate that children added from inside the code-behind are reflected.
        Rectangle newRectangle = new Rectangle() { Width = 50, Height = 50, Fill = Brushes.Orange };
        CanvasSource.Children.Add(newRectangle);
        Canvas.SetLeft(newRectangle, 100);
        Canvas.SetTop(newRectangle, 100);

        CanvasSource.PropertyChanged += CanvasSource_PropertyChanged;

        //Also, demonstrate that child property changes can be seen.
        DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(2) };
        timer.Tick += (sender, args) =>
        {
            timer.Stop();
            newRectangle.Fill = Brushes.Brown;
        };
        timer.Start();
    }

    //This event handler is called when a child is added to or removed from
    //the ObservableCanvas.
    private void CanvasSource_VisualChildrenChanged(object sender, RoutedEventArgs e)
    {
        ObservableCanvas source = sender as ObservableCanvas;
        if (source == null) return;
        CopyElements(source);
    }

    //This event handler is called when a child element of the ObservableCanvas
    //has a property that changes.
    void CanvasSource_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        ObservableCanvas source = sender as ObservableCanvas;
        if (source == null) return;
        CopyElements(source);
    }

    //This event handler is used to ensure that the target Canvas
    //has the elements copied from the source when initialized.
    private void CanvasTarget_Loaded(object sender, RoutedEventArgs e)
    {
        CopyElements(CanvasSource);
    }

    //This function creates a brand new copy of the ObservableCanvas's
    //children and puts it into the target Canvas.
    private void CopyElements(ObservableCanvas source)
    {
        if (CanvasTarget == null) return;

        CanvasTarget.Children.Clear();  //Start from scratch.
        foreach (UIElement child in source.Children)
        {
            //We need to create a deep clone of the elements to they copy.
            //This is necessary since we can't add the same child to two different
            //UIlements.
            UIElement clone = (UIElement)XamlReader.Parse(XamlWriter.Save(child));
            CanvasTarget.Children.Add(clone);
        }
    }
}

Résultats finaux

Voici la fenêtre au démarrage. Il a une copie des éléments de la cible Canvas.

Window - Run Time

Et comme nous avons un changement de propriété 2 secondes après l'initialisation (voir le code derrière), voici la fenêtre après le changement:

Window - Run Time After 2 Seconds

0
Nicholas Miller 5 janv. 2016 à 02:41