Laden...

Für Ziffernblatt/Zeigerinstrument - Zeichnen einer Skala

Erstellt von 7.e.Q vor 16 Jahren Letzter Beitrag vor 14 Jahren 12.291 Views
7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 16 Jahren
Für Ziffernblatt/Zeigerinstrument - Zeichnen einer Skala

Hi Leute,

wie zeichne ich in WPF eine Skala (Ticks) für ein Rundinstrument (Tachometer) mit einem Anfangs- und einem Endwert (bspw. 0 - 280) und großen Ticks in definierten Abständen (0, 20, 40, 60, 80, ..., 260, 280), sowie kleinen Ticks dazwischen (5, 10, 15, 25, 30, 35...)?

Ich vermute, dafür ist die OnRender Methode des vererbten UserControls zu überschreiben. Aber wie zeichne ich dann die Ticks da drin?

Desweiteren... wie sorge ich dafür, daß bei einer Änderung von "Min" (bspw. 0) und "Max" (bspw. 280) des UserControls die Skala neu gezeichnet wird?

Grüße, Hendrik

C
980 Beiträge seit 2003
vor 16 Jahren

OnRender zu überschreiben ist in WPF fast immer der falsche Weg.

Stichworte: Geometry, Shape, Path

http://msdn2.microsoft.com/en-us/library/ms751808.aspx
http://msdn2.microsoft.com/en-us/library/ms747393.aspx

Falls Min/Max dependency properties sind kannst du einfach einen property changed handler setzen (metadata) und darin die Geometrie neu aufbauen.

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 16 Jahren

Hmm, gut, das mit den DependencyProperties ist klar.

Aber ich hab das mit Geometry und so noch nicht begriffen. Wie kriege ich es damit hin, eine vernünftige Skala auf dem Ziffernblatt zu kreieren?

Probleme macht mir auch das ArcSegment. Ich find das blöd mit den Start- und Endpunkten und dem Radius. Dadurch ist es - nach meinem eingerosteten mathematischen Verständnis - unheimlich schwer, einen exakten Mittelpunkt des Kreissegments festzulegen. Ich möchte doch nur einen Kreis haben, der exakt zentriert in dem Control sitzt und dem im unteren Teil ein definiertes Stück fehlt (welches dann durch ein weiteres ArcSegment aufgefüllt wird - siehe Screenshot). Ich mein, gut, das funktioniert so auch, aber der Kreis lässt sich nicht so schön sauber zentriert ausrichten. Ich hab das jetzt durch probieren nach Augenmaß hingeschoben...

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 16 Jahren

Harr! Hab's hinbekommen. 🙂

2.921 Beiträge seit 2005
vor 16 Jahren

Sieht doch gut aus, ich denke jetzt wäre es noch interessant den anderen Usern mitzuteilen, wie DU das gemacht hast. 🙂

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 16 Jahren

Das stimmt. Also da ich einen Wertebereich von Min bis Max habe, hab ich diesen in einer for-Schleife abgedeckt. Ich habe dazu auch noch einen Winkel von -135° bis +135° (0° ist Zeigerstellung senkrecht nach oben - ohne zweideutig sein zu wollen). Ich habe also den Wertebereich von Min bis Max (bspw 0 - 100) auf den Winkelbereich umgerechnet (einfacher Dreisatz). Danach hab ich noch 135 vom Ergebnis abgezogen, um eben nicht 0° - 270°, sondern -135° - +135° zu bekommen. Dadurch hatte ich einen Rotationswinkel. Den habe ich einer RotateTransform Instanz zugewiesen. Dann habe ich in der Mitte des Controls (also an Position 0°) an den entsprechenden Koordinaten eine Line Shape erstellt, ihr den RotateTransform als RenderTransform zugewiesen und die Line an die Children-Collection eines Grid-Elements angehängt. Und das ganze immer wieder, bis alle Linien erstellt waren.

Um die Linien unterschiedlich groß aussehen zu lassen, hab ich einfach überprüft, ob sich der Schleifenzähler durch 5, durch 10 oder durch 20 zählen lässt und entsprechend die Werte gesetzt (mit dem Modulo-Operator geht das gut; einfach schauen, ob das Ergebnis aus Zähler % 20 == 0 ist; für die, die's noch nicht kennen).

Die Zahlen hab ich auf ähnliche Weise platziert. Allerdings nicht mit einem RotateTransform, sondern mit einem TranslateTransform, dem ich als Punkt den transformierten Endpunkt der Ursprungs-Linie gegeben habe (auf Y-Achse minus 10, um die Ziffern ein Stück weiter außen zu platzieren). Und auch da hab ich die Ziffern immer nur erstellt, wenn sich der Schleifenzähler durch 20 teilen lässt (bzw. wenn der erste oder der letzte Schleifendurchlauf erreicht ist, um auch am Anfang und am Ende Ziffern zu haben).

Funktioniert gut. Sieht im Code allerdings reichlich wirr aus:


        private void RenderTicks()
        {
            if (Ticks == null) return; // Falls die Ticks gezeichnet werden sollen, bevor "Ticks" überhaupt existiert 
            // "Ticks" ist das Grid-Control, das die Tickmarks und die Ziffern bekommt.

            Ticks.Children.Clear();

            for (double i = mMin; i <= mMax; i++)
            {
                // mOverallAngle hat den Wert 270.0F; das werde ich noch in einer Property exportieren
                double tAngle = ((mOverallAngle / (mMax - mMin)) * i) - (mOverallAngle / 2);

                // 150.0F, 150.0F ist der Mittelpunkt der gesamten Control (300.0F x 300.0F)
                RotateTransform tTransform = new RotateTransform(tAngle, 150.0F, 150.0F);  // ... und Transformieren der aktuellen Tickmark
            

                TextBlock tBlock = new TextBlock(); // Textblock für die aktuelle Ziffer
                tBlock.Text = String.Format("{0}", i);

                Point tLinePos = tTransform.Transform(new Point(150.0F, 30.0F)); // Position für die aktuelle Ziffer, transformiert zur aktuellen Tickmark
                Size tSize = CalculateTextSize(tBlock.Text, tBlock.FontSize); // Größe berechnen, um Mittelpunkt der Ziffer zu erhalten
                TranslateTransform tMoveScale = new TranslateTransform();
                tMoveScale.X = tLinePos.X - (tSize.Width / 2);
                tMoveScale.Y = tLinePos.Y - (tSize.Height / 2);

                tBlock.RenderTransform = tMoveScale;
                tBlock.Foreground = Foreground;
                tBlock.FontWeight = FontWeight;

                Line tLine = new Line();

                if ((i % 20 == 0 && (mMax - i) > 10) || i == mMax)
                {
                    tLine.Y1 = 50.0F;
                    tLine.Y2 = 40.0F;
                    tLine.StrokeThickness = 3;

                    Ticks.Children.Add(tBlock);
                }
                else if (i % 10 == 0)
                {
                    tLine.Y1 = 50.0F;
                    tLine.Y2 = 40.0F;
                    tLine.StrokeThickness = 1;
                }
                else if (i % 5 == 0)
                {
                    tLine.Y1 = 50.0F;
                    tLine.Y2 = 45.0F;
                    tLine.StrokeThickness = 1;
                }
                else
                {
                    tLine.Y1 = 50.0F;
                    tLine.Y2 = 48.0F;
                    tLine.StrokeThickness = 1;
                }

                tLine.Stroke = Brushes.White;
                tLine.X1 = 150.0F;
                tLine.X2 = 150.0F;
                tLine.RenderTransform = tTransform;

                Ticks.Children.Add(tLine);
            }
        }

        private Size CalculateTextSize(string tString, double tFontSize)
        {
            Typeface tFace = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
            FormattedText tText = new FormattedText(tString, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, tFace, tFontSize, Foreground);
            Size tSize = new Size(tText.Width, tText.Height);
            return tSize;
        }


Man kann da sicherlich noch einiges verbessern (falls ja, was zum Beispiel?). Aber es funktioniert so schonmal sehr gut.

7.e.Q Themenstarter:in
925 Beiträge seit 2004
vor 14 Jahren

Nach langer Zeit grabe ich dieses Ding mal wieder aus. Ein Leser dieses Beitrags hat mich angeschrieben, dass er dieses Control gern für seinen Selbstbau-"Segway" verwenden möchte. Daraufhin hab ich mich des Controls noch einmal an- und eine leichte Überarbeitung des Renderings vorgenommen, da es ein paar Fehler enthielt.

Anbei für alle das kompelette Control als OpenSource. Ich bitte bei Verwendung allerdings um Erwähnung dieses Threads, bzw. meines Namens.