WPF Linie zeichnen in Canvas



  • 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? :p

    Bitte 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.jpg

    Beachte 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.



  • Was ich nicht verstehe, was ist der Vorteil von XAML gegenüber C#? In XAML scheint mir alles deutlich komplizierter und gleichzeitig deutlich eingeschränkter zu sein und am Ende muss der Job dann offensichtlich doch von C# gemacht werden? Auch sehe ich den Vorteil des Retained Mode in WPF nicht ein. Eigentlich sehe ich nur Nachteile (geringere Flexibilität bei gleichzeitig massiv höherem Memory Footprint). Was mich zusätzlich skeptisch macht ist die Tatsache, dass man in die vermeintliche XML Sprache nun auch noch völlig andere und in keiner Weise XML kompatible Sprachkonstrukte reingefrickelt hat ({Binding Source=blahlah} ist nicht eben XML).

    Irgendwie finde ich das Ganze ja interessant. In der Vergangenheit hatte praktisch jede Grafiklibrary in irgendeine Form einen RetainedMode (sogar DirectX bis Version 6 oder 7), der dann aber früher oder später IMMER dem Immediate Mode wich. Hier auch wieder. Ich meine, was ich da vorhabe ist weiss Gott nichts Komplexes und doch scheine ich bereits jtzt an die Grenzen von XAML gestossen zu sein? Schon muss man mit Krücken (IValueConverter) herumfrickeln welche IMHO nicht für diesen Einsatzzweck gedacht waren.

    Wenn ich bereits hier an Grenzen stosse, dann frage ich mich schon, wie ich dann bspw. mit XAML einen Graphen rendern soll, dessen Nodes einen bestimmten Platz (Canvas) optimal nutzen sollen, ohne zur Entwicklungszeit die Anzahl sowie die Dimensionen der Nodes zu kennen. Oder wie soll ich normalisierte Diagramme rendern, wenn ich mit XAML nicht rechnen kann?

    Also im Moment sieht das für mich aus, als wäre XAML gerade gut genug, um ein paar Rechtecke und Ellipsen zu zeichnen, judihui, aber das wars dann auch schon...

    Sehe ich das falsch?


  • Administrator

    Ishildur schrieb:

    Sehe ich das falsch?

    Laut den Experten schon, nach mir nicht wirklich. Ich stimme mit dir nur in einem Punkt nicht überein, in XAML ist viel mehr möglich als in WinForms. Aber es ist exponential komplizierter und meiner Meinung nach auch vielfach unlogisch.

    Es gibt leider immer wieder Dinge, welche nicht durchgezogen sind, nicht fertig sind oder einfach nur einen riesen Aufwand darstellen.

    Wenn man dasselbe erreichen möchte, was man bereits in WinForms erreicht hat, braucht man ca. 1,5 mal so viel Code. Wenn man etwas mehr erreichen möchte, als man in WinForms erreichen konnte, braucht man 10 mal so viel Code. Und dann nochmals ein wenig mehr ist man schon bei 100 mal angekommen. Und darüber muss man dann noch den Überblick behalten und es verstehen.

    Das einzige was wirklich einfach und sehr gut ist, sind einfache Datenbindungsgeschichten. Aber wehe man probiert in dem Bereich das Level ein wenig höher zu setzen, dann stösst man sofort auch an Grenzen.

    Ich habe immer noch keinen Zugang gefunden zu dieser WPF Geschichte, obwohl ich immer wieder damit rumprobiere. Es ist mir ein Rätsel, wie gewisse Leute solche Fans von WPF sein können. Die haben wohl nie mit WinForms programmiert 😉

    Grüssli


Anmelden zum Antworten