WPF Linie zeichnen in Canvas
-
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.
-
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?
-
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
-
First of all - WPF ist keine Grafiklibrary, es is eine Präsentations schicht um Windows Oberflächen zu entwickeln.
Wer sich ernsthaft mit WPF beschäftigt hat wird schnell merken wie fummelig und mühseeelig Forms oder eine C++ UI Library sein kann.
Man fragt sich sehr schnell wieso man sich so lange mit den Alten Zeug aufhielt.Ich füge einfach mal (ein paar!) Beispiele auf:
1. Man kann das gewünschte erzeugen mit nur sehr wenig Code.
Das liegt zum einen daran das es eine recht kompakte Sprache ist, und es eine beliebige Schachtelung erlaubt.<GroupBox> <GroupBox.Header> <CheckBox Content="Advance Settings" /> </GroupBox.Header> </GroupBox>
2. Man kann durch die Binding klasse alles mögliche miteinander vebinden, wenn das Datenformat nicht stimmt kann man es auch konvertieren:
<GroupBox> <GroupBox.Header> <CheckBox Content="Advance Settings" x:Name="advance" /> </GroupBox.Header> <StackPanel> <ComboBox IsEnabled="{Binding IsChecked, ElementName=advance}" ItemsSource="{Binding AnySettings}" /> </StackPanel> </GroupBox>
Nun Zeigt die ComboBox eine Liste von "AnySetting" Objekten, und ist deaktiviert wenn die CheckBox deaktiviert ist.
3. Einfache trennung von Oberfläche und Daten
Angenommen ich habe eine Liste von Personen (Beliebtes Beispiel) und habe diese in einer Listepublic List<Person> Persons { get; set; }
Kann ich nun im Code damit arbeiten ohne das es mich überhaupt interessiert wie es in der UI angezeigt wird, es kann in einer ComboBox sein, einer ListBox, oder was auch immer...
<AnyControl ItemsSource="{Binding Persons}" />
4. Das aussehen von Controls kann an jeder stelle beliebig verändert werden, auch in abhängigkeiten von den Daten.
Beispiel zeige ich unten gleich.5. Man kann mit ein Paar klicks Farbverläufe oder Animationen zusammen basteln. Interessantes Thema, lass ich aber mal außen vor.
6. Die Oberfläche ist Dynamisch.
- Controls passen sich und ihre Größe automatisch an, in MFC und Forms gibt es immer wieder das Problem das Buttons abgeschnitten ist weil der Text zu breit ist.
- Control Inhalte passen den Daten an, wenn sich in den Daten was ändert aktualisiert sich die UI automatisch (Observer Pattern)
- Man kann zur laufzeit die Sprache ändern und der Text ändert sich ohne ein Fetzen CodeMal ein einfaches Beispiel (Was ich hier im Forum tippe, teste nichts).
Ich möchte eine Applikation:
- Oben ein Menü ("File" item, mehr nicht)
- Unten eine Statusleiste ("Ready" text, mehr nicht)
- Über die Statusleiste zwei Buttons (ohne funktionalität) mit OK und Abbrechen auf der Rechnen Seite mit 10 Abstand
- Beide Buttons in jeder sprache gleich lang und ohne das etwas abgeschnitten ist
- Links eine Liste von Personen
- Unterschiedliche Detail ansichten wenn die Person Männlich oder Weiblich ist
- Rechts Detailansicht der ausgewählten Person
- Alles Resizable (Breite und Höhe des Fensters und Breiter machen der Linken liste)Als erstes macht man sich die Daten (Es gibt immer mehrere Lösungswege, das ist einer)
public class Person { public Person(string name) { Name = name; } public string Name { get; set; } } public class MalePerson : Person { public MalePerson(string name) : base(name) { } } public class FemalePerson : Person { public FemalePerson(string name) : base(name) { } }
Nun mache ich die Personen bekannt:
public class Persons { public Persons() { Persons = new List<Person>(); } public List<Person> Persons { get; set; } public void SetPersons(List<Person> persons) { Persons.AddRange(persons); } }
Das sind die Daten, mehr gibt es nicht, mehr braucht man nicht laut Anforderung.
Nun die View
<Window ...> <DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="File" /> </Menu> <StatusBar DockPanel.Dock="Bottom"> <TextBlock Text="Ready" /> </StatusBar> <UniformGrid Rows="1" Margin="10" DockPanel.Dock="Bottom"> <Button Content="OK" IsDefault="True" Margin="5,0,0,0" Padding="12.1" /> <Button Content="Cancel" IsCancel="True" Margin="5,0,0,0" Padding="12.1" /> </UniformGrid> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="150" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <ListBox ItemsSource="{Binding Persons}" x:Name="items"> <ListBox.ItemsTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </ListBox.ItemsTemplate> </ListBox> <GridSplitter Grid.Column="1" ResizeBehavior="PreviousAndNext" Width="4" /> <ContentControl Grid.Column="2" Content="{Binding SelectedItem, ElementName=items}"> <ContentControl.Resources> <DataTemplate DataType="{x:Type Local:FemalePersons}"> <!-- Hier das Template für Weibchen --> </DataTemplate> <DataTemplate DataType="{x:Type Local:MalePersons}"> <!-- Hier das Template für Männchen --> </DataTemplate> </ContentControl.Resources> </ContentControl> </Grid> </DockPanel> </Window>
In Forms hat man da deutlich mehr code (Auch wenns eventuell im Designer Code versteckt ist).
Auch aus C# heraus wäre es um Welten länger.
Man hat deutlich mehr Freiheiten, wie man sieht im Code ist nur noch Datenhalung, sonst nichts.
Wie es Angezeigt wird ist aufgabe der View, sie kümmert sich allein drum. Könnte nun die Personen Liste nach einer ComboBox ändern und nach oben packen und die Details drunter, das würde im Code nichts ändern.@Dravere, nichts deiner aussagen entpricht der Wahrheit! Ich wollte erst einzeln Zitieren, aber die antworten wäre bei jeder einzelnen die selbe gewesen => Bullshit.
Wenn man sich mal damit beschäftigt wie WPF funktioniert, ist alles absolut logisch.
Du kannst viel komplexere Dinge mit Deutlich weniger Code erstellen (Wenn du in Xaml bleibst, du kannst die Oberfläche auch mit C# alleine machen)Kurz gesagt: Du hast absolut keine ahnung was du verpasst und wie schwer du dir das Leben machst wenn du bei Forms bleibst. Nach deiner aussage zu urteilen hast du von WPF an sich absolut keinen Dunst.
Man hat am anfang zwar eine höhere Lernkurve, und man muss auch noch verstehen lernen wie WPF funktioniert, aber das hat man sehr schnell, und dann will man nichts anderes mehr machen.
Man war in seiner Kreativität und in der Software Architektur noch nie freier.//Edit, ein paar Tags vergessen
//Dazu
Was ich noch sagen sollte, das man nicht sagen kann "Des - 20" ist normal, XAML ist eine "Deklarative Sprache", man definiert nur zustände.
"Button Rot, und Ein border als Content mit den Ecken 8 Rund"<Button Background="Red"> <Border CornerRadius="8"> </Border> </Button>
In Triggers ist das auch so
Wenn status = Das, dann Button Gelb und der Border hat keine Ecken mehr.<Trigger Property="status" Value="Das"> <Setter TargetName="button" Property="Background" Value="Yellow" /> <Setter TargetName="border" Property="CornerRadius" Value="0" /> </Trigger>
Methoden gibt es nicht (also auch keine - + operatoren) und auch keine if else ketten in triggers
Insgesammt ist es das was mich zZt dazu bewegt nichts mehr in Foren zu schreiben, die Deppen werden von C# und nun von WPF magisch angezogen.
Da kommen leute ohne ahnung, wollen wie in Forms oder MFC Programmieren, finden das aufwändig und verurteilen WPF an sicht.
KAUFT EUCH EIN BUCH UND LERNT ES in WPF entwickelt man nunmal anders als in Forms, und wenn man das kann weiß man auch warum es deutlich besser, angenehmer, einfacher und vor allem Schneller ist.Solche Leute stehen mir mitlerweile bis hier oben "Buäää, alles so dooof", "Alles so kompliziert", "Geht denn das nicht, scheiß WPF" => LERNT ES DOCH ENDLICH
(Wer sich angesprochen fühlt ist auch gemeint)