WPF Linie zeichnen in Canvas
-
Hallo zusammen
Ich bin gerade dabei WPF zu erlernen und habe diesbezüglich eine ganz einfach Frage, auf die ich aber partout keine Antwort finden kann. Folgende Aufgabe: Zeichne eine Linie von 10,10, bis Canvas.Right-10,10.Wie mache ich denn das jtzt? Ich habs mit DataBindings versucht, aber das scheint nicht zu klappen
Mfg Samuel
-
Hallo Ishildur,
im Prinzip ganz einfach, hättest du bei Google WPF Linie Zeichnen eingegeben, wärst du mit dem ersten Link zur MSDN und den Shapes gekommen, wo alles gut erklärt ist. Ich hab dir trotzdem mal ein Beispiel gemacht (Fast alles aus MSDN kopiert).
Hier ein bisschen XAML:
<Grid> <Canvas Name="myCanvas"> <Line Name="Linie"></Line> </Canvas> </Grid>
Und hier der C# Code dazu, funktioniert wunderbar
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // rufe nach Zeiche Linie, siehe Methode unten. PaintLinie(); } public void PaintLinie() { Linie = new Line(); // Umrissfarbe Linie.Stroke = System.Windows.Media.Brushes.DarkBlue; // Koordinaten festlegen, in deinem Fall wären die 2er dein Canvas Right -10 // falls ich mich jetzt nicht total Irre. Linie.X1 = 1; Linie.X2 = 60; Linie.Y1 = 1; Linie.Y2 = 60; // Ausrichtunng festlegen Linie.HorizontalAlignment = System.Windows.HorizontalAlignment.Left; Linie.VerticalAlignment = System.Windows.VerticalAlignment.Center; // Wie Dick soll die Linie denn sein Linie.StrokeThickness = 3; // Dem Canvas die Linie hinzufügen, als Kind ;) myCanvas.Children.Add(Linie); } }
Grüße
Breaker!
-
Bei Interesse hier noch der MSDN Link, da sind noch viel viel mehr Beispiele zu sämtlichen Shapes
http://msdn.microsoft.com/de-de/library/ms747393.aspx
Gruß
Breaker
-
Hallo Breaker
Herzlichen Dank für deinen Beitrag. Ähmm, wie soll ich das jtzt sagen, ich meinte natürlich mit XAML nicht mit C# (in C# kann ich alles zeichnen ;-)). Mir stellt sich sonst schon die Frage, wozu man XAML brauchen sollte, wenn man letzten Endes doch immer alles in C# machen muss?Ich hätte mir sowas gewünscht:
<LineSegment Point="{Binding ElementName=NameOfCanvas, Path=Right-25},0"/>
Trotzdem danke gell!
-
Ishildur schrieb:
Ich hätte mir sowas gewünscht:
<LineSegment Point="{Binding ElementName=NameOfCanvas, Path=Right-25},0"/>
Ich glaube kaum das du als Path eine Berechnung angeben kannst, oder du müsstest wohl einen Converter schreiben. Du bindest nach Möglichkeiten eine DepentencyProperty an einen Pfad (Und Right-25 ist mit Sicherheit alles, nur keine DepentencyProperty).
-
@asc
Naja, wie gesagt, ich bin gerade dabei WPF zu erlernen und zu evaluieren. Ich möchte etwas IMHO wirklich sehr einfaches zeichnen (also bspw. ein Rechteck 20px vom rechten Rand entfernt zu zeichnen scheint mir nun wirklich keine grosse Herausforderung), offenbar scheint dies jedoch sehr kompliziert oder gar nicht möglich zu sein, sehe ich das richtig? Ich meine in jeder imperativen Sprache entspricht dies einer einzigen Zeile Code und mit XAML muss ich dafür fast ein eigenes Programm schreiben (inkl. mehr als einer Zeile C# code? :pBitte fühl dich nicht angegriffen, ich hinterfrage nur kritisch
P.S.
Deiner Argumentation kann ich natürlich folgen, daher schreibe ich es anders:<LineSegment Point="{Binding ElementName=NameOfCanvas, Path=Right}-25,0"/>
-
Es ist doch ganz einfach. Wenn du etwas bindest kannst du das Gebundene nur über ein Converter bearbeiten.
Du möchtest also eine Linie mit abstand von 10 rechts, oben und links.
Als erstes hat man dann diesen Stand:<Line X1="10" Y1="10" X2="??" Y2="10" Stroke="Black" StrokeThickness="1" />
Nun hast du da mit einem Canvas ein Problem, und war können elemente nicht Stretchen.Dh wenn du ein Canvas behalten willst, musst du einem Converter bemühen.
<Canvas x:Name="myCanvas"> <Line X1="10" Y1="10" X2="{Binding ActualWidth, ElementName=myCanvas, Converter={StaticResource WidthConverter}}" Y2="10" Stroke="Black" StrokeThickness="1" /> </Canvas>
Der Converter muss dann nur die Weite des canvases empfangen, 10 abziehen und zurück geben:
return ((double)value) - 10.0.Wie man ein Converter schreibst findest du wenn du nach "IValueConverter" suchst.
Nun gibt es wenn du kein Canvas nimmst noch andere möglichkeiten, zb ein Grid. Da kannst du nämlich abstände definieren und die Linien stretchen lassen:
<Grid> <Line X1="0" Y1="10" X2="1" Y2="10" Margin="10,0" Stretch="Fill" Stroke="Black" StrokeThickness="1" /> </Grid>
Ein Path würde auch gehen (mit anderen Margins)
<Grid> <Path Margin="10,10,10,0" Data="0.0 L 1.0" Stretch="Fill" Stroke="Black" StrokeThickness="1" /> </Grid>
Oder falls du etwas Trixen magst, nimm ein Border und lass nur die obere linie anzeigen:
<Grid> <Border Margin="10,10,10,0" BorderThickness="0,1,0,0" BorderBrush="Black" Height="1" /> </Grid>
Wie du siehst, es gibt mehrere Wege, je nachdem was du erreichen willst ist das eine oder andere Besser.
PS. Der Code ist nur hier ungetestet im Forum getippt.
-
Hmmm vielleicht habe ich ja einen ganz falschen Ansatz. Ich habe mal einen Screenshot auf nen Webserver geladen: http://www.pandora-studios.ch/wpf/wpf.jpg
Das Problem ist nun, dass der Pfad nicht skaliert, wenn die Grösse des Canvas sich verändert. Doch selbst wenn er dies tun würde, hätte jedes non-uniform scaling eine Verzerrung der beiden oberen Abrundungen sowie der beiden unteren Bezierkurven zur Folge. Um dies zu verhinden müssten bei einem Resize sowohl die beiden Arcs sowie die Kontrollpunkte der Bezierkurven neu berechnet werden. Nur dadürch würden sich Verzerrungsartefakte verhindern lassen.Mit besten Grüssen
Samuel
-
Für Controls die automatisch Skalieren kannste ein Canvas vergessen. In einem Canvas kannst du nur Fixe punkte setzen und das wars.
Was du machen kannst, zeichne dein Pfad in einer gedachten 100x200 Größe gehostet in einem Grid, und setz Stretch auf Fill. Den Abstand bekommst du dann mit Margins hin.
Nun kannst du das Fenster Resizen, das Grid resized sich dann auch und dein Pfad vergrößert sich automatisch mit.Probiers aus
1. Pack dein Path statt in einem Canvas so wie es ist in einem Grid
2. Setz Path.Stretch auf Fill
3. Definiere ein Path.Margin für den Abstand
-
@David W
Ich probiere das gleich mal aus, allerdings bin ich mir ziemlich sicher, dass dadurch genau die von mir genannten Verzerrungsartefakte bei jedem non-uniform scaling entstehen werden...
-
Ja, verzerrt :p
-
Dann erlaubste halt nur ein Uniform Fill
-
Dann läuft die Applikation nur noch mit einem einzigen Seitenverhältnis korrekt
-
Was für artefakte treten eigentlich auf?
-
http://www.pandora-studios.ch/wpf/wpf.jpg
http://www.pandora-studios.ch/wpf/artefakt0.jpg
http://www.pandora-studios.ch/wpf/artefakt1.jpgBeachte vor allem die oberen Abrundungen (nix mehr rund sein) sowie natürlich auch
die Bezierkurve, welche jeweils eine völlig andere Form annimmt.
-
Und was erwartest du?
-
Ich will natürlich den Pfad so definieren können, dass diese Artefakte nicht entstehen, so wie ich das mit jeder anderen mir bekannten Grafikbibliothek (GDI,GDI+,DirectX,OpenGL,AWT/SWING) auch tun kann!
Bspw. anstatt
<LineSegment Point="200,10"/> <ArcSegment Point="210,20" Size="10,10" SweepDirection="Clockwise"/>
bräuchte ich sowas:
<LineSegment Point="Canvas.Right-20,10"/> <ArcSegment Point="Canvas.Right-20,20" Size="10,10" SweepDirection="Clockwise"/>
usw..
Wenn man es so definiert, entstehen diese Artefakte nicht!
Nur leider scheint das mit WPF oder zumindest mit XAML nicht möglich zu sein?
-
Das beantwortet die Frage nicht.
Angenommen du machst das Fenster größer - was soll Passieren?
-
Ach so
Horizontale Skalierung:
- Obere horizontale Linie stretchen, rechte obere Rundung verschieben (keine Skalierung!)
- Polynomkoeffizienten für die Kontrollpunkte der Bezierkurven neu berechnen um Kurvenform wiederherzustellen (bei grösserer horizontaler Ausdehnung, müssen die Kontrollpunkte auf der vertiaklen Achse weiter auseinanderliegen).Vertikale Skalierung:
Nur die beiden vertikalen Linien stretchen, Kontrollpunkte der Bezierkurve werden vertial verschoben (keine Neuberechnung erforderlich).
-
Dh also du willst beim Stretch nur die Horizontalen und vertikalen Linien stretchen lassen, die ecken aber nicht.
Xaml only geht das nicht (müsste man partiell vom stretch aus nehmen).
Was du aber machen kannst ist das von mir bereits angesprochene konvertieren.<Window.Resources> <Local:LengthConverter x:Key="LengthConverter" /> </Window.Resources> <Grid x:Name="head"> <Path Margin="10"> <Path.Effect> <DropShadowEffect ShadowDepth="1" BlurRadius="3" /> </Path.Effect> <Path.Data> <PathGeometry> <PathFigure StartPoint="10,20"> <PathSegmentCollection> <ArcSegment Point="20,10" Size="10,10" SweepDirection="Clockwise" /> <LineSegment Point="{Binding ActualWidth, ElementName=header, Converter={StaticResource LengthConverter}, ConverterParameter=10.0}" /> <ArcSegment Point="210,20" Size="10,10" SweepDirection="Clockwise" /> <LineSegment Point="210,50" /> <BezierSegment Point1="180,25" Point2="100,50" Point3="100,50" /> <BezierSegment Point1="100,49" Point2="40,70" Point3="10,50" /> <LineSegment Point="10,20" /> </PathSegmentCollection> </PathFigure> </PathGeometry> </Path.Data> <Path.Fill> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FF0066CB" /> <GradientStop Color="#FF99CCFF" Offset="1" /> </LinearGradientBrush> </Path.Fill> </Path> </Grid>
public class LengthConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { double controlWidth = (double)value; double secondValue = System.Convert.ToDouble(parameter); // hier berechnen wie die werte sein sollen return new Point((controlWidth) - 20.0, secondValue); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
Du könntest nun für jeden verschiedenen typen einen eigenen converter schreiben.
Oder im ConverterParameter angeben welche Linie es ist, der Converter kann dann in ein Switch Case die Werte neu berechnen.
(Der Converter wird bei jedem Resize des Grids aufgerufen.)
Im Xaml hast du eventuell am ende nur noch Zahlen die Fest sind.//Dazu
Das Code Beispiel macht gerade kein Sinn, es zeigt aber wie du das gewünschte erreichen kannst.