Laden...

Truncated Texts von aussen in WPF-Applikation ermitteln

Erstellt von dr4g0n76 vor 2 Jahren Letzter Beitrag vor 2 Jahren 932 Views
dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren
Truncated Texts von aussen in WPF-Applikation ermitteln

Hallo zusammen.

Gibt es eine Möglichkeit automatisch abgeschnittene Texte in WPF Applications von aussen zu erkennen?

Was meine ich damit:

  • "von aussen" heisst hier in einem externen Programm. Nicht in der Anwendung selbst. Denn die exe darf dazu nicht verändert werden (nicht von mir bestimmt).
  • Folgende Lösungen wären gut und legitim in diesem Fall (also eine davon reicht natürlich):
    a) Ein Programm, dass das aufzeigen kann per Screen oder wenn man mit der Maus über das entsprechende WPF Control fährt, das Text anzeigt.
    b) Ein fertiges Testtool, dass dies kann. Könnte auch kostenpflichtig in diesem Fall sein. Kostenlos wäre besser.
    b) Eigene Lösung. Ich hab den Teil mit den Textlängen ermitteln schon entwickelt.
    Was hier fehlen würde: Wieviele Zeichen können angezeigt werden in der mit der Mouse gehoverten Control?
    Denn dann könnte man auch den Unterschied zwischen angezeigten und eingegebenen Textlängen berechnen und daraus ermitteln, wieviel Platz man tatsächlich zur Anzeige hat.

Ging es vielleicht auch damit?


private Size MeasureString(string candidate)
{
    var formattedText = new FormattedText(
        candidate,
        CultureInfo.CurrentCulture,
        FlowDirection.LeftToRight,
        new Typeface(this.textBlock.FontFamily, this.textBlock.FontStyle, this.textBlock.FontWeight, this.textBlock.FontStretch),
        this.textBlock.FontSize,
        Brushes.Black,
        new NumberSubstitution(),
        1);

    return new Size(formattedText.Width, formattedText.Height);
}

Würde das reichen um vom gehoverten Control und dem eingegebenen Text ausrechnen zu können, wieviele Zeichen tatsächlich angezeigt wurden und wieviele eingegeben wurden?

Ich hoffe, dass das so verständlich ist.

Nochmal kurz ganz anders ausgedrückt:

Es geht darum, dass wir einfach ermitteln können, wie lang ein Text sein kann, in einem entsprechenden Control, damit er nicht abgeschnitten werden kann.
Dazu wird ein Programm benötigt, dass dies von aussen ermitteln kann.

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

2.079 Beiträge seit 2012
vor 2 Jahren

Es gibt Programme, die WPF-Anwendungen von "außen" untersuchen und live bearbeiten können.
Früher habe ich dafür den WPF Inspector genutzt, aber den gibt's leider nicht mehr.
Vor kurzem habe ich stattdessen SnoopWpf verwendet und das hat sehr gut funktioniert.

Schau dir mal den Source an, da kannst Du dir sicher einige Tricks abschauen.
Aber ich vermute mal, dass das eine ziemlich komplizierte Angelegenheit werden kann.
Oder das Tool bietet eine API, das habe ich mir nicht angeschaut.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

Hallo Palladin007.

Danke für diese Idee. SnoopWpf hab ich mir sogar tatsächlich schon angeschaut, bevor ich hier die Frage gepostet hatte.

Ich denke, was funktionieren könnte, ist folgender Ansatz:

  • Text der gehoverten Control ermitteln [gelöst]
  • Font bestimmen (sollte hoffentlich vom ermittelten Automation Control aus möglich sein. Mal schauen…
  • die Grösse der Textbox bzw. des Controls bestimmen. Falls überhaupt in WPF vorhanden,
    sowas wie ClientRectangle, bzw. was dem entspricht. Zumindest mal die Breite. Dann wäre es für einzeilige Texte lösbar.
    Allein das wäre schon unglaublich hilfreich.

Möglicherweise geht das so:


var hwndList = (IntPtr)(int)(listWindow.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty));
var listRect = (Rect)listWindow.GetCurrentPropertyValue(AutomationElement.BoundingRectangleProperty);

  • Dann mit Font und MeasureString über den String candidate ausrechnen, wie viel exceeded wird.
    Oder z. B. die Breite durch die Anzahl Zeichen teilen. Je nachdem, wie das nachher genau ausschaut.
    Dies sollte dann wiederum zumindest für Fonts, bei denen jedes Zeichen die gleiche Breite hat funktionieren.
    Was bei uns momentan zum Glück der Fall ist.

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

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

Weiss eigentlich jemand, wie man das in der eigenen App machen würde?
Egal welche Control man anzeigt, die Text(e) auf dem Bildschirm anzeigt?

Damit könnte man vielleicht auch Rückschlüsse ziehen, wie man das von aussen mit Windows Automation ermittelt.

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

T
111 Beiträge seit 2005
vor 2 Jahren

Hallo

Ich habe mal bei einem WPF-Projekt eine TextBox eingesetzt, die bei zu langem Text diesen abschneidet und durch "..." ersetzt. Dieses ist auf CodeProject zu finden. Vielleicht hilft Dir das ja.

mfG
Thomas

2.079 Beiträge seit 2012
vor 2 Jahren

Für mich klingt das nicht so, als wäre es hier eine Option, das Projekt zu bearbeiten 🙂
Aber klar, wenn es nur darum geht, Texte schön abgeschnitten darzustellen, der Source da ist und man ihn bearbeiten kann/darf, dann ist natürlich eine angepasste TextBox das Optimum.

Ich hab damit genau 0 Erfahrung und kann auch nur wieder zu Spoon verweisen ^^

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

@Thomas.at

Ja, das Problem ist, dass das Konzept bei der Übersetzung nicht richtig durchdacht wurde.
Und das mit den Ellipsen ist zwar eine gute Idee und wird teilweise so gemacht.

Warum ich das so vorgeschlagen hatte, die abgeschnitten Texte (truncated texts) so zu finden, durch Berechnung oder was auch immer es sonst noch für Möglichkeiten geben möge,
ist, dass eben in anderen Sprachen, wenn die Ellipsis benutzt wird (also die "…"-Funktion), dann wird in anderen Sprachen zu viel abgeschnitten.

Z. B. kann in Deutsch/Englisch Notalarm/Emergency Alarm geschrieben werden, nur wenn dann in Spanisch angezeigt wird Alarma de Emergencia
und man nur Alarma de … sieht, dann kann das zu Problemen führen. Normalerweise hätte ich gesagt: Dann vergrössert doch einfach die Controls.
Da die ganze Applikation aber nachher auf einem Panel läuft, mit 1366 x 720 als Auflösung, ist halt teilweise der Platz auf manchen Bildschirmen sehr beschränkt.

Leider scheint sich halt darüber keiner vorher Gedanken gemacht zu haben. Wie so oft…

Ausserdem dachte ich könnte das allgemein eine gute Funktion eines Testtools sein.

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

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

@Palladin007

Klar, übrigens hab ich mir das mal genauer angeschaut, was Controls ermitteln angeht, lohnt es sich sowohl

SnoopWpf

und auch

Accessibility Insights

anzuschauen für WPF unter Windows 10 usw.
Denn das zeigt auch die ganzen Boxes mit Focus auf dem gehoverten Control an.
Hab gerade geschaut, man kann auch nachschauen, wie es das macht.

https://cloudblogs.microsoft.com/opensource/2019/03/12/microsoft-open-sources-accessibility-insights/

🙂 Das sollte auf jeden Fall helfen sehr nahe an die Lösung zu kommen, die Controls zu ermitteln.

Mit dem Abschneiden, muss dann immer noch geschaut werden. 🙂

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

2.079 Beiträge seit 2012
vor 2 Jahren

Warte mal ... Du könntest die TextBoxen bearbeiten?
Warum dann dieser irre Aufwand?

Sowas kann man problemlos global an alle TextBoxen anhängen.
Wie genau man das nun macht, müsste man sich nochmal im Detail anschauen (es gibt sicher fertige Lösungen), aber allein das suchen der "Problemfälle" sollte mit einer AttachedProperty einfach sein. Die kann man mit einem globalen Style überall setzen und dann den Text und die Maße überwachen lassen - und mit den Daten tust Du dann Dinge.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

@Palladin007

Wofür steht eigentlich die 007 bei Dir? Seriennummer Skateboard? James Bond? Geburtstag in spezieller Form? Deine Serien ID. ^^

Ich könnte, wenn ich hier teil vom Softwareteam wäre, bzw. nicht nur die Textcontrols. Denn das wäre genau, das was ich machen würde und den Vorschlag habe ich auch schon eingebracht.
Nur... leider bin ich hier "nur" im Test-Team. Deswegen werden meine Vorschläge hier auch nicht entgegengenommen. Und dann fragt sich halt das Software-Team, wie sie die ganzen abgeschnittenen Texte finden sollten.

Deswegen kam ich aus dem Testerkontext her auf die Idee: Ich könnte ja alle Controls durchgehen und dann schauen.
Leider ist die Softwarequalität so schlecht, dass selbst wenn ich mein eigenes Programm einhängen würde, das nicht garantiert ist, dass das funktioniert.

Denn die App wird quasi "vorsichtig hochgecrasht", sorry aber anders kann ich das bei der Softwarequalität nicht mehr bezeichnen.
Denn es entstehen erst mal ein paar hundert Exceptions, bis Host und Exe laufen. LOL.

Jetzt weiss auch, warum dieser IRRE Aufwand. Denn abgeschnittene Texte wird es immer wieder geben und ich will den Aufwand für mich automatisieren.
Hab keinen Bock, das immer wieder händisch durchzukontrollieren.

Jetzt kennst Du den Hintergrund. 🙂

Hab mir eh neulich schon anhören dürfen: Du findest immer so komische Bugs.
Meine Antwort dazu:

  1. Dazu wurde ich ja hierhergeholt.
  2. Wenn Du so komische Sachen programmierst... Und ausser mir scheint sie ja keiner bisher zufinden...

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

T
111 Beiträge seit 2005
vor 2 Jahren

Hallo

@dr4g0n76

Ich habe den Link eigentlich deshalb gepostet, vielleicht kannst Du hier Dir etwas abschauen, wie da die Berechnung gemacht wird, ob der Text gekürzt werden muss oder nicht. Daraus müsste sich doch dann ergeben, ob der Text ins Feld passt oder nicht.

mfG
Thomas

2.079 Beiträge seit 2012
vor 2 Jahren

Mir kam gerade noch eine Idee ...

Die Main-Methode wird bei WPF ja generiert und besteht eigentlich aus nur drei Zeilen:


public static void Main()
{
    var app = new App();
    app.InitializeComponent();
    app.Run();
}

Starte die Anwendung nicht "normal", sondern lade die Assembly zur Laufzeit in dein eigenes Programm.
Dann suchst Du nach der Application-Ableitung (hier "App"), instanziiere sie und führe damit den Code oben aus.
Damit hast Du ein Programm, das dynamisch WPF-Anwendungen nachladen und im eigenen Prozess gekapselt ausführen kann.
Du musst nur die Threads richtig einstellen (STA, ggf. noch mehr), dann ist das kein Problem - hab ich vor einigen Wochen mal für ein anderes Ziel gemacht, dann aber wieder verworfen.

Wenn die Anwendung läuft, hast Du durch die Application-Instanz direkten Zugriff auf alles, inklusive der Fenster, dem Visual-Tree, den Resourcen, etc.
Auf der Basis kannst Du ohne viel Debugger-Magie nach allem suchen, was Du so brauchst.

Wenn bei euch die Main-Methode nicht nur aus den drei Zeilen oben besteht, wird's schwieriger 😉
Aber auch da hätte ich eine Idee, z.B. könntest Du eine zweite Main2-Methode zur Laufzeit im RAM generieren (System.Reflection.Emit) und dann ausführen.
Du musst dabei größtenteils nur den Code von der originalen Main-Methode rüber schreiben - bis auf das "new App()", das lässt Du weg und verwendest stattdessen einen Parameter.
So kannst Du dann wieder die Instanz erzeugen, in einem eigenen Thread der Main2-Methode übergeben und die Daten überwachen.

PS:
Mein erster Versuch nach 15 Minuten:


var appFilePath = @"<...>\bin\Debug\net6.0-windows\WpfApp1.dll";
var appAssembly = Assembly.LoadFile(appFilePath);
var appType = appAssembly.GetTypes()
    .Single(t =>
    {
        var type = t.BaseType;

        while (type is not null && type.FullName != "System.Windows.Application")
            type = type.BaseType;

        return type is not null;
    });
var appInstance = (Application)Activator.CreateInstance(appType)!;
appType.GetMethod("InitializeComponent")!.Invoke(appInstance, null);
appInstance.Run();

In der csproj muss <UseWpf>true</UseWpf> stehen.

Er schafft es bis zum "Run", dann vermisst er die baml-Resourcen, aber das kann man sicher auch zurecht tricksen ^^.

Wofür steht eigentlich die 007 bei Dir? Seriennummer Skateboard? James Bond? Geburtstag in spezieller Form? Deine Serien ID. ^^ Keine Ahnung - ist ewig alt und ich hab's nie geändert.

2.079 Beiträge seit 2012
vor 2 Jahren

[STAThread]
static void Main(string[] args)
{
    var appFilePath = @"<...>\WpfApp1.dll";

    var app = LoadWpfApplicationFrom(appFilePath);

    app.LoadCompleted += (s, e) =>
    {
        Console.WriteLine("Loaded");

        foreach (Window window in app.Windows)
        {
            if (window == app.MainWindow)
                Console.Write("Main-");
            Console.WriteLine("Window: " + window.Title);
        }
    };

    app.Exit += (s, e) => Console.WriteLine("Exit");

    app.Run();
}

private static Application LoadWpfApplicationFrom(string filePath)
{
    var appAssembly = Assembly.LoadFile(filePath);

    // Find Application-Implementation
    var appType = appAssembly.GetTypes()
        .Single(t =>
        {
            var type = t.BaseType;

            while (type is not null && type.FullName != "System.Windows.Application")
                type = type.BaseType;

            return type is not null;
        });

    // Set ResourceAssembly
    const BindingFlags BindingFlags_PrivateStatic = BindingFlags.Static | BindingFlags.NonPublic;
    typeof(BaseUriHelper).GetProperty("ResourceAssembly", BindingFlags_PrivateStatic)!.SetValue(null, appAssembly);
    typeof(Application).GetField("_resourceAssembly", BindingFlags_PrivateStatic)!.SetValue(null, appAssembly);

    // Create and initialize Application-Implementation
    var app = (Application)Activator.CreateInstance(appType)!;
    appType.GetMethod("InitializeComponent")!.Invoke(app, null);

    return app;
}

Mehr schlecht als recht hin gerotzt, aber es tut, was es soll.
Wenn Du die App in einem anderen Thread startest (STA nicht vergessen), kannst Du auch nebenher andere Dinge machen. Bedenke aber aber, dass außerhalb des UI-Threads nicht viel erlaubt ist.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

Leider wird bei uns kein App Object benutzt, wie ich gerade gesehn hatte. Aber das lässt sich ja ändern, wenn ich das ganze benutze, um zu testen.
Ich werde es auf jeden Fall ausprobieren. War auch gerade am Suchen, wie man das mit dem BAML löst. Du warst schneller. 🙂

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

2.079 Beiträge seit 2012
vor 2 Jahren

Leider wird bei uns kein App Object benutzt, wie ich gerade gesehn hatte.

Ohne Application-Objekt kein WPF.
Oder ich würde gerne den Code dazu sehen, da hat wohl jemand grob fahrlässig alles umgebaut, was geht.

Vermutlich ist es einfach nicht der normale Start, das geht ja recht einfach zu ändern, aber irgendwann gibt's immer ein Application-Objekt.
Und die Klasse hat auch eine statische Current-Property, auch kein ThreadStatic, damit könntest Du aus jedem Thread die Instanz abrufen.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

Ich kann Dir zumindest einen Teil davon zeigen.

Es wurde durch


using <OWN NAMESPACE>;

namespace <OWN NAMESPACE>
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var applicationManager = new ApplicationManager();

            applicationManager.Start("OFApplication","OFSplashScreen");
        }
    }
}

Ausgetauscht und die ApplicationManager-Klasse leitet weder von Application ab, noch macht sie sonst was WPF-trächtiges. Nada. Ist auch nicht von etwas anderem abgeleitet und hat auch kein Interface.

Sieht teilweise so aus:


 public class ApplicationManager
    {
        private string _splashScreenType = "Default";

        private Window _splashScreen;
        private string _applicationName;
        private ManualResetEvent _delay = new ManualResetEvent(false);
        private ManualResetEvent _splashSync = new ManualResetEvent(false);

        public ApplicationManager()
        {

        }

Und startet Threads über etwas das ein wenig so aussieht wie ein Factorypattern...

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

2.079 Beiträge seit 2012
vor 2 Jahren

Irgendwann muss es aber ein Application-Objekt geben.
Du musst einen Weg finden, darauf zu warten 😉
Danach müsstest Du es über die Application.Current-Property finden können.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

Das auf jeden Fall. Nur... wass willst Du erwarten, bei einer Software die erstmal 100e von Exceptions erzeugt, bis sie wirklich hochfährt.

Ich hab mal versucht folgendes zu machen:

Es gibt eine Art try/catch-Block für die ganze Applikation. Mit diesem eigens vergewalteten Application-Object, dass die Entwickler selbst "verbrochen" haben. 😉
Ich hab mal den try/catch-Block entfernt, weil zumindest in VS2019 manche Sachen nicht einfach so einsehbar waren, durch Warning-Suppressions and what not.
Selbst wenn Du den weg machst, das Ding ist so instabil, dass die Software dann nicht läuft.

Wie kann man so eine Qualität machen. Aber egal... (egal im Sinne von dann distanzier ich mich halt emotional komplett davon, was kann ich dafür. 😉)


try
{
   Application.StartMainProgram();
}
catch(Exception ex)
{
   Log(ex).Here();
}

und Du hast Recht. Ich hab das Application-Object auch noch nicht gefunden.
Das einzige wo ich bisher Application.Current gesehen hatte, war in einer Drag/Drop-Routine. Morgen hab ich endlich mal etwas mehr Luft, dann werde ich mir es genauer anschauen.

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

2.079 Beiträge seit 2012
vor 2 Jahren

Es hat nichts damit zu tun, dass Du ein Application.Current-Objekt siehst oder nicht, sondern dass WPF das von sich aus macht.
Sie müssten schon die WPF eigene Start-Route nach entwickeln, um ohne Application-Objekt auszugehen und davon gehe ich mal nicht aus.

Und wenn Du nicht weist, wann es das Objekt gibt: Bau einen Button, der das Application-Objekt sucht und untersucht, den drückst Du dann, wenn alles fertig geladen ist.

dr4g0n76 Themenstarter:in
2.921 Beiträge seit 2005
vor 2 Jahren

Das ist tatsächlich so. Der Code ist … na ja. Bisher. Es wird gerade viel umgeändert.
Und vor allem auch das, dass es wie eine normale WPF-App funktioniert.

Auf jeden Fall in der neuesten Version gibt es das jetzt mehr oder weniger zumindest bis jetzt.

Deshalb habe ich mal angefangen, den Vorschlag von Dir auszuprobieren.
Danke nochmals dafür!

Ich werde auf jeden Fall versuchen, das Ganze zum Laufen zu bringen.
Und dann den Code hier ergänzen. Zuerst mal für den allgemeinen Fall.
Denn das Thema muss ich sowieso lösen. Bei uns brennt's halt gerade an vielen Ecken und Enden.
Hier gilt wirklich wieder mal, aufgeschoben ist nicht aufgehoben.

P.S: Ich hab also den Code übernommen und mal versucht erste Schritte zu machen.
Ich werde ein Update hier posten, sobald ich ein paar Schritte weitergekommen bin.

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