Problem mit Xaml-Designer

  • WPF

Es gibt 18 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Problem mit Xaml-Designer

    Mir wurde mehrfach die sog. CommandMap empfohlen, und macht auch einen sehr guten Eindruck, und die Laufzeit funktioniert auch.
    Nur im Xaml-Designer zickt das Teil bei mir rum, und da weiß ich jetzt nix anderes, als euch mal zu fragen, ob ihr das im Anhang mal ausprobieren könnt, und mir rückmelden, ob euer Xaml-Designer auch spinnt, oder obs mein System ist, was iwie verdaddelt ist.

    Also die CommandMap ist eine Klasse, wo man viele DelegateCommands reinschmessen kann, und dann kann man direkt im Xaml daran binden, und muss nicht für jeden Click eine Property erstellen, mit getter, private setter, backing-Field und Initialisierung der RelayCommands.
    BeispielCode einer CommandMap mit 3 Commands:

    C#-Quellcode

    1. public CommandMap Commands { get { return _Commands; } }
    2. private CommandMap _Commands;
    3. public void AnAction(object o) {
    4. AText += "\nAn Action!";
    5. }
    6. public void AnotherAction(object o) {
    7. AText += "\nAnother Action!";
    8. }
    9. public void ThirdAction(object o) {
    10. AText += "\nThird Action!";
    11. }
    12. public MainViewModel() {
    13. _Commands = new CommandMap();
    14. _Commands.AddCommand("AnAction", AnAction);
    15. _Commands.AddCommand("AnotherAction", AnotherAction);
    16. _Commands.AddCommand("ThirdAction", ThirdAction);
    17. }

    Wie gesagt: Laufzeit wunderbar, nur inne Designzeit schrottets den Xaml-Designer:


    Edit: Tja, wie's aussieht, habich den Bug gefixt.
    Hab das ganze Kommentar-Gelaber entfernt, und an einigen Stellen was optimiert, und nun hatter aufgehört mit spinnen, und ich weiß eiglich nicht, warum ?(



    Edit: Schließlich hat sich das Mysteriöseste geklärt, und letztendlich kannich eine glaub bessere Alternative präsentieren.
    Die Verbesserungen kann man noch endlos weiter treiben, als nächstes würde ich ein DelegateCommand erfinden, was seine Beschriftung, Icon etc. selber weiß, dann kann man im Xaml Templates dafür machen, die mit einem einzigen Binding ordentlich gestaltete MenuItems und Buttons hinhauen, inkl Text, Icon, Tooltip, Command natürlich, Commandparameter, Accessor-Key.
    Den ganzen Kram kann man dann dynamisch vom Viewmodel aus steuern, so in die Richtung.
    Aber erstmal habich mit klein Reflection erreicht, dass ein Command seinen Namen selber weiß, man den also nicht mehr eintippen muss beim Adden:

    C#-Quellcode

    1. public MainViewModel() {
    2. _Commands = new CommandMap();
    3. _Commands.Add("AnonymousCommand", x => MessageBox.Show("AnonymousCommand"));
    4. _Commands.Add(AnAction);
    5. _Commands.Add(AnotherAction);
    6. _Commands.Add( ThirdAction, o => _AText.Length > 30);
    7. }
    8. public string AText { get { return _AText; } set { ChangePropIfDifferent(value, ref _AText, "AText"); } }
    9. private string _AText = "AText";
    10. public void AnAction(object o) {
    11. AText += "\nAn Action!";
    12. }
    13. public void AnotherAction(object o) {
    14. AText += "\nAnother Action!";
    15. }
    16. public void ThirdAction(object o) {
    17. AText += "\nThird Action!";
    18. }
    Funzt natürlich nicht bei anonymen Methoden.
    Dateien

    Dieser Beitrag wurde bereits 9 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Bei mir (Visual Studio Ult 2013) selbes Problem nach dem ersten Erstellen.
    Funktionieren tut es wieder wenn man den TypeDescriptionProvider von der CommandMap entfernt.

    Was das jedoch für Auswirkungen hat (in dem Fall funzen keine Header Clicks mehr), kann ich nicht sagen, da ich solche TypeDescriptors noch nie benötigt habe und auch ned weis wozu man die braucht.

    Eventuell kannst du da aber ansetzen. (stackoverflow.com/questions/86…reate-an-instance-of-type)

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „fichz“ ()

    Hallo @ErfinderDesRades,
    habe mir deine Solution runtergeladen und getestet.

    Nachdem ich alle Dateien einmal in meinem Visual Studio geöffnet habe (VS2015 CE) bekomme ich 2 Fehlermeldungen.
    ​Fehler 1 Der Name "MainViewModel" ist im Namespace "clr-namespace:CommandMapTester.Viewmodel" nicht vorhanden. D:\Downloads\CommandMapTester\CommandMapTester\App.xaml 11 5 CommandMapTester
    Fehler 2 Der Name "MainViewModel" ist im Namespace "clr-namespace:CommandMapTester.Viewmodel" nicht vorhanden. D:\Downloads\CommandMapTester\CommandMapTester\App.xaml 1 1 CommandMapTester


    Erstelle ich nun dieses Projekt bzw. die Datei, sind beide Fehler weg und ich habe auch keine weiteren mehr in meiner Fehlerliste,.. So viel zu dem geschehen in meiner Umgebung.

    Welches VS nutzt du zur Zeit?

    Gruß,
    Drahuverar
    Option Strict On!
    Ja, bei den paar Dingen die ich bisher gemacht habe war das auch so. Wollte Dir nur sagen wie und was bei mir passiert ist. :saint:

    Also ad hoc könnte man behaupten, dass irgendwas in dem Hintergrund des VS2015 'gewurschtelt' wird, da in meiner Version kein Problem entsteht.
    Bisher haben wir ja nur @fichz und meine Umgebung getestet..wäre also vielleicht ein voreiliger Entschluss.
    Option Strict On!
    Also ich hab den Bug jetzt ganz eng eingekreist: Er tritt auf, wenn die CommandMap im Namespace CommandMapTester liegt, er ist weg, wenn die CommandMap im Namespace PersistingWorkflows liegt. ;( .
    Dazu folgender Code, der ermöglicht, mit einer Copy-Replace-Aktion den CommandMap-Namespace umzuschalten:

    C#-Quellcode

    1. namespace CommandMapTester { }
    2. namespace PersistingWorkflows { }
    3. namespace PersistingWorkflows {
    4. [TypeDescriptionProvider(typeof(CommandMapDescriptionProvider))]
    5. public class CommandMap {
    Die leeren Namespaces darüber tun nix, dienen nur als Text zum Auskopieren. Alles andere kann unverändert bleiben, nur CommandMapTester eintragen, und das Teil fängt an zu spinnen.
    Das Spinnen teste ich, indem ich alle Fenster schließe, rekompiliere, dann Window1 öffne, und bei einem MenuItem das CommandBinding entferne und wieder hinmache.
    Beim Hinmachen erscheint bei den Fehlermeldungen der NullReference-Fehler.

    Die Sample-Solution dazu in Post#1.
    Vielen Dank fürs Feedback, derzeit stehen wir 2:3 (aus anderer Quelle kam noch ein 2015-Tester hinzu), dasses ein Bug ist.

    Dazu noch nächste Frage:
    Konzeptionell soll die Commandmap auch das Setzen von Bindings im Property-Fenster unterstützen.
    Tut sie bei mir nur zur Hälft. Also die CommandMap an sich finde ich im PropertyFenster natürlich vor - weil ist ja eine Public Property des MainViewmodels. Daber das TypeDescriptor-Gedöhns hat ja eiglich den Sinn, dass man nun auch die einzelnen Commands anwählen kann.

    Also bitte um erneutes Feedback: kann man die in die CommandMap reingeschmissenen Commands bei euch tatsächlich auch im Xaml-Property-Fenster als BindungsQuelle wiederfinden?

    Ich kann auch nochmal Bildle machen:

    Ich hab extra eine Extra-Klasse so geschrieben (Commands2), dass man sehen kann, wie die Commandmap sich eigentlich im Propertyfenster präsentieren sollte.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    Ich hab extra eine Extra-Klasse so geschrieben (Commands2), dass man sehen kann, wie die Commandmap sich eigentlich im Propertyfenster präsentieren sollte.
    Bei mir sieht's so aus

    Aber aus welcher Solution soll man das testen?
    Ich habe beide im Post #1 angeschaut, da sehe ich nirgend wo etwas von Commands2?
    Danke fürs Bildle :thumbsup:

    Und du musst natürlich die Solution nehmen, die ich hier auf Platte habe!

    äh - ups! - (update in post#1 :saint: )

    Also es steht 2:0, dass das PropertyFenster failt, zumindest in 2013.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    fichz schrieb:

    Funktionieren tut es wieder wenn man den TypeDescriptionProvider von der CommandMap entfernt.
    naja, dann funzt der Designer wieder, aber die Bindings sind dann natürlich beim Deibel.

    fichz schrieb:

    ... ich ... ned weis wozu man die braucht.
    Jetzt weisstes: Man kann Klassen wie die CommandMap schreiben, die im Designer und fürs Bindungssystem "so tun als ob" sie Properties hätten.
    Weil die Commands, die man in die CommandMap schmeisst, sind natürlich keine Properties von dieser. Aber dennoch kann man daran binden, und das funzt halt mit diesem TypeDescriptor-Trick, der dem Bindungssystem vorgaukelt, da seien Properties, obwohls in Wirklichkeit nur Einträge in einem Dictionary sind.

    Wiedemauchsei, das noch verbleibende Fehlverhalten ist, dass diese Vorgaukelei nur zur Laufzeit funzt (na immerhin!).
    Aber leider nicht zur Designzeit, was eiglich möglich sein sollte (zumindest in WinForms geht das).
    Hi. Ohne die Solution runtergeladen zu haben: Hier steht im Note Kasten, dass DataBinding für das Interface (und damit auch für die benutzte CustomTypeDescriptor Klasse) im Designer nicht unterstützt wird. Es steht zwar nicht explizit da dass der WPF Designer gemeint ist, aber da es mit ihm Probleme gibt wird es wohl daran liegen.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „shad“ ()

    Jo, sieht sehr danach aus.
    Der Unterschied ist halt, dass die Note sich auf Klassen bezieht, die ICustomTypeDescriptor implementieren, während hier die CustomDescripterei ja über Attribute klargemacht wird. Aber ich nehme stark an, dass wenn die schon keine Implementierer unterstützen, dass die dann Attributierte erst recht nicht unterstützen.
    Ausserdem sieht das stark nach einem viel einfacheren Ansatz aus, eine CommandMap zu basteln: nämlich statt der Attribute-Orgie einfach ICustomTypeDescriptor implementieren und gut ist.

    super! Dann kann ich den Thread jetzt als erledigt markieren :) (naja, vlt. komm ich noch mit der neuen CommandMap über)

    Weil zwischenzeitlich habich auch rausgefunden, was diese mysteriöse Xaml-Exception ausgelöst hat:

    C#-Quellcode

    1. public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
    2. return new CommandMapDescriptor(base.GetTypeDescriptor(objectType, instance), instance as CommandMap);
    3. }
    instance as CommandMap ergibt bei Type-Mismatch null, und das gibt an der nächsten Ecke einen Folge-Fehler, und damit die irreführende NullReferenceException.
    In Wirklichkeit ist's ja ein InvalidCast, und der ist schwer verdaulich, aber letztendlich aufschlussreich:

    InvalidCastException schrieb:

    Quellcode

    1. [A]CommandMapTester.CommandMap cannot be cast to [B]CommandMapTester.CommandMap.
    2. Type A originates from 'CommandMapTester, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' at location 'C:\Users\Admin\AppData\Local\Microsoft\VisualStudio\12.0\Designer\ShadowCache\itnwb5qf.umg\ixfqa1ds.5e5\CommandMapTester.exe'.
    3. Type B originates from 'CommandMapTester, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' at location 'C:\Users\Admin\AppData\Local\Microsoft\VisualStudio\12.0\Designer\ShadowCache\31nn0cns.dre\x4byozui.5ff\CommandMapTester.exe'.


    Wenn man das genau studiert kommt man drauf, dass er einen Typen aus ieinem Cache nicht umwandeln kann in einen Typen aus einem anderen Cache.
    Offsichtlich cached der Xaml-Designer geladene typen, und setzt den Cache auch nicht zurück, wenn man zwischendrin rekompiliert.
    Muss man wissen, dann macht man VS vor jedem Neu-Versuch zu und wieder auf 8|
    Und mein "BugFix" ist vermutlich eher ein Zufallsprodukt, weil wenn man nicht mehr am Code rumfummelt bleibt der Fehler ja weg (und wenn der Fehler weg ist, fummelt man nicht mehr dran rum).

    Jdfs Lehre: Exceptions sind unsere Freunde, und TryCast (c#: as - Schlüsselwort) ist bei unsachgemäßer Verwendung eine Fehlerquelle, die zu ganz lausigen Folge-Fehlern führt :cursing:

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    zur Info: Die erste Version der vereinfachten CommandMap ist draussen.
    Achso - kann ja zeigen, wie's nun aussieht, ohne Attribute-Heckmeck-Brimborium:

    C#-Quellcode

    1. #region FileHeader
    2. #if false
    3. Two classes:
    4. 1) CommandMap derives from CustomTypeDescriptor and overrides the GetProperties-Method. For that Bindings can query, which "Properties" are available.
    5. 2) CommandPropertyDescriptor exposes Informations about those Properties. Most important is the GetValue()-Method, to imitate the Property-Getter.
    6. #endif
    7. using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows.Input;
    8. #endregion //FileHeader
    9. namespace CommandMapTester {
    10. public class CommandMap : CustomTypeDescriptor {
    11. public readonly Dictionary<string, ICommand> Map = new Dictionary<string, ICommand>();
    12. /// <summary> create, add and return a DelegateCommand </summary>
    13. public DelegateCommand Add(Action<object> executeMethod, Predicate<object> canExecuteMethod = null) {
    14. var commandName = executeMethod.Method.Name;
    15. if (commandName.StartsWith("<")) throw new NotSupportedException("can't derive commandName from anonymous method. Use the other overload");
    16. return Add(commandName, executeMethod, canExecuteMethod);
    17. }
    18. /// <summary> create, add and return a DelegateCommand </summary>
    19. public DelegateCommand Add(string commandName, Action<object> executeMethod, Predicate<object> canExecuteMethod = null) {
    20. var cmd = new DelegateCommand(executeMethod, canExecuteMethod);
    21. Map[commandName] = cmd;
    22. return cmd;
    23. }
    24. /// <summary> for internal use, to support Databinding </summary>
    25. [EditorBrowsable(EditorBrowsableState.Never)]
    26. public override PropertyDescriptorCollection GetProperties() {
    27. var props = Map.Select(kvp => new CommandPropertyDescriptor(kvp.Key)).ToArray();
    28. return new PropertyDescriptorCollection(props);
    29. }
    30. /// <summary> private class, which describes the "Properties", which are stored in the Dictionary </summary>
    31. private class CommandPropertyDescriptor : PropertyDescriptor {
    32. public CommandPropertyDescriptor(string name) : base(name, null) { }
    33. public override bool IsReadOnly { get { return true; } }
    34. public override bool CanResetValue(object component) { return false; }
    35. public override Type ComponentType { get { throw new NotImplementedException(); } }
    36. public override object GetValue(object component) {
    37. return ((CommandMap)component).Map[this.Name];
    38. }
    39. public override Type PropertyType { get { return typeof(ICommand); } }
    40. public override void ResetValue(object component) { throw new NotImplementedException(); }
    41. public override void SetValue(object component, object value) { throw new NotImplementedException(); }
    42. public override bool ShouldSerializeValue(object component) { return false; }
    43. }//CommandPropertyDescriptor
    44. }
    45. }

    update-info für interessierte:
    Ich hab das noch sehr viel weiter-getrieben, und auch gleich ein code-project-Artikel zu verzapft:

    codeproject.com/Articles/10718…usage-of-DelegateCommands

    Mit meim CommandTree-Teil kann man nun ganze Menü-Bäume im Viewmodel zusammenstöpseln, und das Xaml zeigt das komplette Menü an, ohne dass man auch nur ein einziges MenuItem aufs Window packen müsste.