Fragensteller
Kann TransformGroup auf ein Canvas und all seine Children gesetzt werden

Frage
-
Hallo, ich habe folgendes Problem:
bin dabei mir eine Oberfläche für ein kleines Zeichentool zu erstellen.
Das Problem:
Ich habe ein Canvas definiert, in dem ich Zeichnen möchte.
Wenn ich ein Zylinder oder ähnliches Zeichne, dann sollte man direkt mit definierten Nullpunkten areiten können.
Ich stelle mir das so vor, das ich zuerst eine globale Drehung der Vorzeichen für X- und Y-Achse mache (Scaletransform).
Dann lege ich den Nullpunkt fest, in Y-Achse auf Fenstermitte, in X-Achse 100 Einheiten vom rechten Rand entfernt (TranslateTransform).
Diese beiden Transformationen fasse ich in einer TransformGroup zusammen.
Diese TransformGroup weise ich dann dem Canvas zu. Dann denke ich mir, das alle Elemente die ich dem Canvas zuweise, automatisch nch dieser TransformGroup beim zufügen gezeichnet werden.
Ist aber nicht so, das passt nicht.
Mache ich einen Denkfehler?
Anbei der Code:public MainWindow() { InitializeComponent(); //// Transformationen festlegen //// Transformation von Stackpanel, damit die Y-Achse mit positiver Richtung nach oben verläuft Basistransformation.ScaleTransformGlobal = new ScaleTransform(){ScaleX = 1, ScaleY = -1}; //// Nullpunktverschiebung in X- und Y-Koordinate festlegen //// X-Nullpunkt 20 Einheiten von linkem Rand //// Y-Nullpunkt in vertikaler Mitte Basistransformation.NullpunktverschiebungGlobal = new TranslateTransform((Canvas.Width / 2.0) + 20, -Canvas.Height / 2.0); //// Transformgruppe ertellen, dieser die Y-Achsenspiegelung zufügen und dann die TransformGroup dem Canvas zuweisen Basistransformation.TransformGroupGlobal = new TransformGroup(); Basistransformation.TransformGroupGlobal.Children.Add(Basistransformation.ScaleTransformGlobal); Basistransformation.TransformGroupGlobal.Children.Add(Basistransformation.NullpunktverschiebungGlobal); //// Dem Canvas die globale Transformation zuweisen (Vorzeichenänderung X/Y-Achse, Nullpunktverschiebung) Canvas.LayoutTransform = Basistransformation.TransformGroupGlobal; } private void Btn_Test_OnClick(object sender, RoutedEventArgs e) { //// Path definieren Path pthTest = new Path(){Stroke = Brushes.Red, StrokeThickness = 4}; //// PathFigure definieren PathFigure pthFgrTest = new PathFigure(){StartPoint = new Point(0,0)}; pthFgrTest.Segments.Add(new LineSegment(new Point(200,20), true)); //pthFgrTest.Segments.Add(new LineSegment(new Point(200, -250), true)); //pthFgrTest.Segments.Add(new LineSegment(new Point(200, -250), true)); //// PathGeometry definieren, zuweisen vpn PathFigure PathGeometry pthGeoTest = new PathGeometry(); pthGeoTest.Figures.Add(pthFgrTest); //// Dem Path die PathGeomtry zuweisen, pthTest.Data = pthGeoTest; //pthTest.LayoutTransform = Basistransformation.TransformGroupGlobal; //// Dem Canvas den PAth mit den Zeichnungsinformationen zuweisen Canvas.Children.Add(pthTest); }
oema von MSDN
Alle Antworten
-
Hallo,
was genau ist denn Basistranformation?
Ich habe deinen Code mal so umgebaut das er das macht was er soll.
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) { //// Transformationen festlegen //// Transformation von Stackpanel, damit die Y-Achse mit positiver Richtung nach oben verläuft var st = new ScaleTransform() { ScaleX = 1, ScaleY = -1 }; //// Nullpunktverschiebung in X- und Y-Koordinate festlegen //// X-Nullpunkt 20 Einheiten von linkem Rand //// Y-Nullpunkt in vertikaler Mitte var tt = new TranslateTransform(20, Canvas.ActualHeight / 2.0); //// Transformgruppe ertellen, dieser die Y-Achsenspiegelung zufügen und dann die TransformGroup dem Canvas zuweisen var tg = new TransformGroup(); tg.Children.Add(st); tg.Children.Add(tt); //// Dem Canvas die globale Transformation zuweisen (Vorzeichenänderung X/Y-Achse, Nullpunktverschiebung) Canvas.RenderTransform = tg; }
Problematisch ist zunächst, dass du niemals mit der UI im Konstruktor interagieren solltest. Das kannst du im XAML oder aber frühestens im Load-Event tun. Denn im Konstruktor hat dein Canvas noch eine Größe von 0*0. Weiterhin musst du ActualHeight bzw. ActualWidth auslesen, sofern die Größe nicht explizit zugewiesen wurde.
Da du das TranslateTranform nach dem ScaleTransform anwendest musst du dort auch schon die Drehung mit einbeziehen. Daher muss der Offset-Wert für y positiv sein. Dein Y-Offset würde das Zentrum 20px nach rechts von der Mitte verschieben. Außerdem musst du hier RenderTransform und nicht LayoutTransform zuweisen.Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets -
Hallo. die Basisklasse hält die Variablen der Transformationen vor, so war das gedacht.
public class Basistransformation { /// <summary> /// Transformation für generelle Verwendung auf Objekt. /// </summary> public static ScaleTransform ScaleTransformGlobal { get; set; } /// <summary> /// Globale Nullpunktverschiebung /// </summary> public static TranslateTransform NullpunktverschiebungGlobal { get; set; } /// <summary> /// Globale Transformgruppe /// </summary> public static TransformGroup TransformGroupGlobal { get; set; } }
Danke Tom. Werde das gleich mal testen.
oema von MSDN
-
Hallo Tom, das mit der Tranformation passt nun so. Das hatte ich auch schon so probiert mit "RenderTransform". Allerdings wird dann meine Menuleidte nicht korrekt gezeichnet, es ist ein Stück davon abgeschnitten. Wenn ich das Fenster maximiere fehlt beinahe die komplette Menuleiste. Ursache?
oema von MSDN
-
Hallo,
RenderTransform bewirkt ein richtiges verschieben des Controls in der gerenderten Ansicht. Entsprechend verdeckt das Canvas ggf. Elemente wenn es über diesen liegt.
Daher verschiebt man üblicher weiße auch nicht den Koordinatenursprung. Es gibt immer wieder unangenehme Seiteneffekte. Zumal das Anpassen der Elementkoordinaten auch nicht so viel Arbeit bedeutet.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets -
Hallo Tom. Das Problem ist bestimmt folgendes: Ich zeichne ein Objekt (die Linie, oder dann weitere Elemente) über einen Button-Click. Da müßte dann das komplette Canvas neu gerendert werden. Kan das mein Problem sein? Wie veranlasse ich dann nach jedem Button-Click das neue Rendern?
oema von MSDN
-
Hi,
ich würde das Canvas als WKS (Weltkoordinatensystem ansehen). Jedes Element wird dem Canvas hinzugefügt und entsprechend Lage im WKS transformiert (Größe und Ausrichtung). Für eine Veränderung der Darstellung (Zoom, Blickrichtung) würde ich ein BKS (Benutzerkoodinatensystem) nutzen, d.h. transformiert wird nur das Canvas und damit automatisch alle Innereien.Zu Deiner Frage ist erst einmal zu klären, was Du mit "Ich zeichne ein Objekt" meinst. Wenn damit gemeint ist, dass ein neues Objekt dem Canvas hinzugefügt wird, dann kann mit einem PropertyChanged und string.Empty die gesamte Oberfläche zum Neumalen aufgefordert werden. Eine Veränderung des BKS bewirkt automatisch ein Neuzeichnen.
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut -
Hallo Peter, ich möchte eine interaktive Oberfläche gestalten.
Der User wählt aus was er zeichnen will. Einen Bogen (ArcSegment), Linie und gibt dann die Positionen ein (manchmal auch nur die Endpositionen. Per Button "Element anfügen" wird dann das element an das vorige angefügt (ich fürde PathFigure verwenden, da kann ich die PathFigureSegmentCollectioin verwenden).
Zum Schluss kann dann die Form geschlpssen werden.
Deine Meinung: das Koordinatensystem so belassen, das neue Objekt erstellen (PathFigure) und diesem dann die gewünschte Transformation zuweisen (X-/Y-Achse drehen, Nullpunktverschiebung). Habe ich das so richtig verstanden??
Will der User später oder irgendwann zoomen, dann die Transformationsmatrix des Containers der die PathFigures (Path'es) beinhaltet verändern?? Habe ich das auch richtig verstanden?
Dann dazu die Frage zum neuzeichen: PropertyChanged, auf was bezogen soll ich das aufrufen? auf den Container/Panel mit den gezeichneten Objekten?
Versteht mich nicht falsch, aber ich muss jetzt da Schritt für Schritt vorgehen, damit mir das mal richtig klar wird.
oema von MSDN
-
Hi,
ich würde nicht nur mit Anfügen an Path.. arbeiten. Das Anfügen ist nur interessant, wenn Polylinien zu zeichnen sind. Wenn unabhängige Elemente zu zeichnen sind, dann ist es besser, diese zu erzeugen und den Children's des Canvas hinzuzufügen. Vorher sollte aber das hinzuzufügende Element skaliert und gedreht werden, so dass es sich passend im WKS befinden. Für die gesamte Darstellung im BKS ist das Canvas zu skalieren und zu drehen. Wenn Du nur mit Children und gebundener ObservableCollection arbeitest, dann brauchst Du kein NotifyPropertyChanged. Hier mal eine Demo für das Malen einzelner Linien, wie ich das meine:XAML:
<Window x:Class="WpfApplication1CS.Window22" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApplication1CS" mc:Ignorable="d" Title="Window22" Height="300" Width="500"> <Window.Resources> <local:Window22VM x:Key="vm"/> <Style TargetType="Label"> <Setter Property="Margin" Value="5"/> </Style> <Style TargetType="TextBox"> <Setter Property="Margin" Value="5"/> </Style> <Style TargetType="Button"> <Setter Property="Margin" Value="5"/> </Style> <Style TargetType="Slider"> <Setter Property="Margin" Value="5"/> </Style> <Style TargetType="Border"> <Setter Property="Margin" Value="5"/> </Style> </Window.Resources> <Grid DataContext="{Binding Source={StaticResource vm}}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <Label Content="X1 X2 Y1 Y2 W1"/> <TextBox Text="{Binding X1}" Width="30"/> <TextBox Text="{Binding Y1}" Width="30"/> <TextBox Text="{Binding X2}" Width="30"/> <TextBox Text="{Binding Y2}" Width="30"/> <TextBox Text="{Binding W1}" Width="30"/> <Button Content="Linie hinzufügen" Command="{Binding Cmd}"/> </StackPanel> <Border Grid.Row="1" BorderBrush="Red" BorderThickness="2"> <Canvas local:Window22CanvasAssistant.BoundChildren="{Binding Liste}"> <Canvas.LayoutTransform> <TransformGroup> <ScaleTransform CenterX="100" CenterY="100" ScaleX="{Binding ElementName=slZoom, Path= Value}" ScaleY="{Binding ElementName=slZoom, Path= Value}"/> <RotateTransform CenterX="100" CenterY="100" Angle="{Binding ElementName=slRotate, Path=Value}"/> </TransformGroup> </Canvas.LayoutTransform> </Canvas> </Border> <Grid Grid.Row="2"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> <ColumnDefinition Width="Auto"/> <ColumnDefinition /> </Grid.ColumnDefinitions> <Label Content="Zoom"/> <Slider Name="slZoom" Minimum=".2" Maximum="5" Value="1" Grid.Column="1" /> <Label Content="Drehung" Grid.Column="2"/> <Slider Name="slRotate" Minimum="0" Maximum="90" Value="0" Grid.Column="3" /> </Grid> </Grid> </Window>
Und der Code dazu (ViewModel und attached Property-Klasse);
using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; namespace WpfApplication1CS { public class Window22VM { public int X1 { get; set; } = 10; public int Y1 { get; set; } = 10; public int X2 { get; set; } = 100; public int Y2 { get; set; } = 100; public int W1 { get; set; } private ObservableCollection<UIElement> _liste = new ObservableCollection<UIElement>(); public ObservableCollection<UIElement> Liste { get { return this._liste; } } public ICommand Cmd { get { return new RelayCommand(CmdExec); } } private void CmdExec(object obj) { Line l = new Line() { X1 = X1, Y1 = Y1, X2 = X2, Y2 = Y2, Stroke = Brushes.Black, StrokeThickness = 2 }; if (W1 != 0) l.LayoutTransform = new RotateTransform(W1); this._liste.Add(l); } }
internal static class Window22CanvasAssistant { public static readonly DependencyProperty BoundChildrenProperty = DependencyProperty.RegisterAttached("BoundChildren", typeof(object), typeof(Window22CanvasAssistant), new FrameworkPropertyMetadata(null, onBoundChildrenChanged)); public static void SetBoundChildren(DependencyObject dependencyObject, string value) => dependencyObject.SetValue(BoundChildrenProperty, value); private static void onBoundChildrenChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { if (dependencyObject == null) return; var canvas = dependencyObject as Canvas; if (canvas == null) return; var objects = (ObservableCollection<UIElement>)e.NewValue; if (objects == null) { canvas.Children.Clear(); return; } objects.CollectionChanged += (sender, args) => { if (args.Action == NotifyCollectionChangedAction.Add) foreach (object item in args.NewItems) canvas.Children.Add((UIElement)item); if (args.Action == NotifyCollectionChangedAction.Remove) foreach (object item in args.OldItems) canvas.Children.Remove((UIElement)item); }; foreach (UIElement item in objects) canvas.Children.Add(item); } } }
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut- Bearbeitet Peter Fleischer Freitag, 26. Februar 2016 09:05
- Als Antwort vorgeschlagen Aleksander Chalabashiev Donnerstag, 3. März 2016 14:18
-
Hallo Peter, das ist echt sch.... ;-), aber da muss ich jetzt durch.
Ich bin dir echt dankbar für deine Idee, und ich denke nun auch das die XAML-Geschichte ihre Vorteile hat (ich mach eigentlich sehr viel in CodeBehind, besser aber man kann beides). habe mal probiert das von dir in ein Projekt zu packen damit ich mir ein Bild davon machen kann wie das spielt, Fehlanzeige da sind noch Bugs drin das es kompiliert werden kann.
Habe das Projekt gezippt. Kann ich dir das irgendwie zukommen lassen, dann kannst mal kurz Hand anlegendamit es kopiliert werden kann?
Als anhang geht das ja nicht.
Dann muss ich mir das auch wieder in MVVM ansehen (habe damit auch schon was gemacht).
oema von MSDN
-
Hi,
Du kannst meinen Code einfach in ein neues (leeres) WPF-Projekt kopieren, welches WpfApplication1CS heißt und ein Startfenster mit dem Namen Window22 hat.Um mir Programmcode zu senden, schicke mir einfach eine Mail mit dem Zugang zu Deinem TFS an meine Adresse (s. Impressum auf meiner Homepage).
--
Viele Grüsse
Peter Fleischer (MVP, Partner)
Meine Homepage mit Tipps und Tricks
Kommas richtig setzen!
Schüler sagen, Lehrer haben es gut.
Schüler, sagen Lehrer, haben es gut