Laden...

Enum.HasFlag (oder wo liegt mein Fehler?)

Erstellt von jbrown vor einem Jahr Letzter Beitrag vor einem Jahr 664 Views
J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr
Enum.HasFlag (oder wo liegt mein Fehler?)

Hallo,

mir konkreter Titel ist mir nicht eingefallen.

Zu meinem Problem:
Mein MessageViewModel stellt eine Auflistung IEnumerable<Base_MessageModel> bereit.
Die MessageView dazu wird an mehreren Stellen im Programm verwendet und erstellt
aus der o.g. Quelle immer eine CollectionView:


<DataGrid ItemsSource="{Binding Path=View, ElementName=ThisUC, UpdateSourceTrigger=PropertyChanged}">


public partial class UC_Message : UserControl
    {
        public UC_Message()
        {
            InitializeComponent();

            _FilterItemClickCommand = new DelegateCommand<object>((s) =>
            {
                var _typeOfMessage = (MessageTypeEnum)s;

                switch (Filter.HasFlag(_typeOfMessage) == false)
                {
                    case true:
                        Filter |= _typeOfMessage; break;

                    default:
                        Filter &= ~_typeOfMessage; break;
                }
                                
                Debug.WriteLine("active filter @CommandExecute: " + Filter);
                View?.Refresh();
            },
            (s) => { return true; });
        }


public static readonly DependencyProperty ViewProperty = DependencyProperty.Register("View", typeof(ICollectionView), typeof(UC_Message));
        public ICollectionView View
        {
            get { return (ICollectionView)GetValue(ViewProperty); }
            set
            {
                SetValue(ViewProperty, value);
            }
        }
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(IEnumerable<Base_MessageModel>), typeof(UC_Message), new PropertyMetadata(OnSourceChanged));
        public IEnumerable<Base_MessageModel> Source
        {
            get { return (IEnumerable<Base_MessageModel>)GetValue(SourceProperty); }
            set
            {
                SetValue(SourceProperty, value);
            }
        }

private static void
            OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var uc = (UC_Message)sender;

            /* set view */
            switch (uc.Source == null)
            {
                case true:
                    uc.View = null; break;

                default:
                    {
                        uc.View = CollectionViewSource.GetDefaultView(uc.Source);
                        uc.View.Filter = uc.Filter_Function;
                        break;
                    }
            }
        }

Ziel ist, dass jede Instanz der View in der Lage ist die gleiche Datenbasis bei Bedarf unterschiedlich zu filtern. Dazu hier noch etwas um dies zu können:


public static readonly DependencyProperty Filter_IsEnabledProperty = DependencyProperty.Register("Filter_IsEnabled", typeof(bool), typeof(UC_Message), new PropertyMetadata(true));
        public bool Filter_IsEnabled
        {
            get { return (bool)GetValue(Filter_IsEnabledProperty); }
            set
            {
                SetValue(Filter_IsEnabledProperty, value);
            }
        }

        public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(MessageTypeEnum), typeof(UC_Message), new PropertyMetadata(Filter_Changed));
        public MessageTypeEnum Filter
        {
            get { return (MessageTypeEnum)GetValue(FilterProperty); }
            set
            {
                SetValue(FilterProperty, value);
            }
        }

private static void
            Filter_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var uc = (UC_Message)sender;

            Debug.WriteLine("active filter @FilterChanged: " + uc.Filter); // <---------------- nur zum prüfen was hier los ist
        }

Die Filter Funktion sieht so aus und vermutlich liegt hier das Problem. Debug.WriteLine gibt
hier stets active filter @FilterFunction: 0 zurück was ich mir beim besten Willen einfach
nicht mehr erklären kann.


private bool
            Filter_Function(object messageObject)
        {
            if (Filter_IsEnabled == false)
                return true;

            var message = messageObject as Base_MessageModel;

            Debug.WriteLine("active filter @FilterFunction: " + Filter); //<------------------------ warum ist Filter hier immer (int)0

            return Filter.HasFlag(message.Target);
        }

Die Enumeration dazu sieht wie folgt aus:


[Flags]
    public enum MessageTypeEnum
    {
        //[Description("Kein Filter")]
        //None = 0,

        [Description("Anwendung")]
        Application = 1,

        [Description("Distributor")]
        Distributor = 2,

        [Description("Setup")]
        Setup = 4,

        [Description("Debugger")]
        Debug = 8,

        [Description("Baumer Simulator")]
        BaumerSimulator = 16,

        [Description("Wolke Simulator")]
        WolkeSimulator = 32,        
    }

Einer dieser Enumerationwerte wird dann ausschließlich hier ergänzt / entfernt:


_FilterItemClickCommand = new DelegateCommand<object>((s) =>
            {
                var _typeOfMessage = (MessageTypeEnum)s;

                switch (Filter.HasFlag(_typeOfMessage) == false)
                {
                    case true:
                        Filter |= _typeOfMessage; break;

                    default:
                        Filter &= ~_typeOfMessage; break;
                }
                                
                Debug.WriteLine("active filter @CommandExecute: " + Filter);
                View?.Refresh();
            },
            (s) => { return true; });
        }

Schalte ich nun nacheinander die Filter "Application, Distributor, Setup und Debug" an, sieht die Ausgabe so aus:
[Debug.WriteLine Ausgabe]


active filter @FilterFunction: 0
active filter @FilterChanged: Application
active filter @CommandExecute: Application
active filter @FilterFunction: 0
active filter @FilterChanged: Application, Distributor
active filter @CommandExecute: Application, Distributor
active filter @FilterFunction: 0
active filter @FilterChanged: Application, Distributor, Setup
active filter @CommandExecute: Application, Distributor, Setup
active filter @FilterFunction: 0
active filter @FilterChanged: Application, Distributor, Setup, Debug
active filter @CommandExecute: Application, Distributor, Setup, Debug

Frage: Warum ist die "Filter" - Property in der Funktion "Filter_Function" immer 0 und macht den Filter damit wirkungslos ?

T
50 Beiträge seit 2010
vor einem Jahr

Ich habe das Gefühl, dass hier die Logik vertauscht ist. Darüber hinaus ist ein switch-Block ist für ein bool meist ungeeignet, nutze hier einen if-Block.


switch (Filter.HasFlag(_typeOfMessage) == false)
{
    case true:
        Filter |= _typeOfMessage; break;
    default:
        Filter &= ~_typeOfMessage; break;
}

Sollte wohl eher so aussehen:


if (Filter.HasFlag(_typeOfMessage))
{
    Filter &= ~_typeOfMessage; // entfernen, wenn bereits vorhanden
}
else
{
    Filter |= _typeOfMessage;  // hinzufügen, wenn noch nicht vorhanden
}

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

Das kann ich soweit akzeptieren, hilft mir aber leider nicht beim Problem.

4.939 Beiträge seit 2008
vor einem Jahr

Wo rufst du denn Filter_Function (bzw. uc.View.Filter) auf und welchen Parameter übergibst du?

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

Hier erfolgt nur die Zuweisung des Filter Predikats:


/* set view */
            switch (uc.Source == null)
            {
                case true:
                    uc.View = null; break;

                default:
                    {
                        uc.View = CollectionViewSource.GetDefaultView(uc.Source);
                        uc.View.Filter = uc.Filter_Function; //<------------------------------------------
                        break;
                    }
            }

Über ein DataProvider werden die Enumerationswerte in eine Liste geholt, in einem Menu
dargestellt, angeklickt und dadurch FilterItemClickCommand aufgerufen.


<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="MessageTypeProvider">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="helper:MessageTypeEnum" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>

<Button Content="Filter anpassen"...
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Source={StaticResource MessageTypeProvider}}">
                            <ContextMenu.ItemContainerStyle>
                                <Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MahApps.Styles.MenuItem}">
                                    <Setter Property="StaysOpenOnClick" Value="True"/>
                                    <Setter Property="Foreground" Value="{StaticResource MyBlue}"/>
                                    <Setter Property="Header" Value="{Binding Converter={StaticResource GetEnumDescriptionConverter}}"/>
                                    <Setter Property="IsCheckable" Value="true"/>
                                    <Setter Property="Command" Value="{Binding Data.FilterItemClickCommand, Source={StaticResource ThisUCProxy}}"/>
                                    <Setter Property="CommandParameter" Value="{Binding}"/>
                                </Style>
                            </ContextMenu.ItemContainerStyle>
                        </ContextMenu>
...
...

Filter_Function dürfte nur hier gezielt durch Refresh aufgerufen werden!?


_FilterItemClickCommand = new DelegateCommand<object>((s) =>
            {
                var _typeOfMessage = (MessageTypeEnum)s;
                                
                if (Filter.HasFlag(_typeOfMessage))
                {
                    Filter &= ~_typeOfMessage; // remove it
                }
                else
                {
                    Filter |= _typeOfMessage;  // add it
                }

                Debug.WriteLine("active filter @CommandExecute: " + Filter);
                View?.Refresh(); //<---------------------------------------------------------
            },
            (s) => { return true; });

Das MessageModel selbst sieht so aus:


public class Base_MessageModel
    {
        public Base_MessageModel(MessageTypeEnum _target, string _message)
        {
            Target = _target;
            RawMessage = _message;
            Date = DateTime.Now;
        }

        public Base_MessageModel(MessageTypeEnum _target, EndPoint _clientEndPoint, EndPoint _serverEndPoint, string _message)
        {
            Target = _target;
            RawMessage = _message;
            ClientEndPoint = _clientEndPoint;
            ServerEndPoint = _serverEndPoint;

            Date = DateTime.Now;
        }

        public MessageTypeEnum Target { get; private set; }

        private string _RawMessage;
        public string RawMessage
        {
            get { return _RawMessage; }
            set
            {
                _RawMessage = value;
                Message = Get_PrintableMessage(value);
            }
        }
        public string Message { get; private set; }
...
...

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

Evtl. noch interessant. Zeigt immer korrekt die gerade
gesetzten Filter an.


<TextBlock DockPanel.Dock="Right" Foreground="{StaticResource MyBlue}" Margin="20,0"
 Text="{Binding Filter, ElementName=ThisUC,FallbackValue=noFilter, StringFormat={}Aktive Filter: {0}}" VerticalAlignment="Center"/>

4.939 Beiträge seit 2008
vor einem Jahr

Was ich meine, ist, ob du jeweils das gleiche UC_Message-Objekt benutzt (oder doch verschiedene)?

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

Da UC_Message selbst die View ist und ich diese wie üblich instanziiere, glaube ich das gleiche Objekt zu nutzen, ja.


<local:UC_Message Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                          Title="MELDUNGEN"
                          Source="{Binding Path=Data.MessageViewModel.Messages, Source={StaticResource MainViewModel}, UpdateSourceTrigger=PropertyChanged}"
                          Margin="5,0,5,5"/>

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

OK, Danke Th69. Das war dann wohl der entscheidende Hinweis.


<local:UC_Message Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                          Title="MELDUNGEN"
                          Source="{Binding Path=Data.MessageViewModel.Messages, Source={StaticResource MainViewModel}, UpdateSourceTrigger=PropertyChanged}"
                          Margin="5,0,5,5"/>

Dies hatte ich wie gesagt in insgesamt mehreren SubViews so integriert. Nachdem ich nun alle
Source - Bindings bis auf einen auskommentiert habe, funktionierts. Ich weiß nur noch
nicht warum... Weil die zugrundeliegende Source - Auflistung im MessageViewModel die selbe ist?

4.939 Beiträge seit 2008
vor einem Jahr

Du kannst ja mal in jedem SubView einen anderen UC_Message.Title benutzen (und diesen dann zusätzlich beim Debug ausgeben).

PS: Aus deinem switch (bool) solltest du ein if () else machen.
Und ich würde die View erst erzeugen und dann zuweisen:


var view = CollectionViewSource.GetDefaultView(uc.Source);
view.Filter = uc.Filter_Function;
uc.View = view;

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

Guten Morgen,

Deine Hinweise habe ich umgesetzt, obwohl ich beim 2ten nicht
wüsste warum das sinnvoller ist !?

Aus deinem switch (bool) solltest du ein if () else machen.
Und ich würde die View erst erzeugen und dann zuweisen:

Zu meinem Kernproblem bleibt der Aha Effekt noch immer aus.

  • Source aller 3 Views sind aktiv gebunden
  • Zum Start der Anwendung stehen bereits 3 - 4 Meldungen an.
  • Filter wie folgt aktiviert
  • Es greift nur der Filter in der DebugView

Der Reihe nach aktiviert:
View Teamleiter: Application, Distributor, Setup
View Debug: Application, Distributor, Setup


USER        @FilterFunction:  Filter: 0
TEAMLEITER  @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
TEAMLEITER  @FilterChanges    Filter: Application
TEAMLEITER  @CommandExecute   Filter: Application
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
TEAMLEITER  @FilterChanges    Filter: Application, Distributor
TEAMLEITER  @CommandExecute   Filter: Application, Distributor
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
TEAMLEITER  @FilterChanges    Filter: Application, Distributor, Setup
TEAMLEITER  @CommandExecute   Filter: Application, Distributor, Setup
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterFunction:  Filter: 0
DEBUG       @FilterChanges    Filter: Application
DEBUG       @CommandExecute   Filter: Application
DEBUG       @FilterFunction:  Filter: Application
DEBUG       @FilterFunction:  Filter: Application
DEBUG       @FilterFunction:  Filter: Application
DEBUG       @FilterChanges    Filter: Application, Distributor
DEBUG       @CommandExecute   Filter: Application, Distributor
DEBUG       @FilterFunction:  Filter: Application, Distributor
DEBUG       @FilterFunction:  Filter: Application, Distributor
DEBUG       @FilterFunction:  Filter: Application, Distributor
DEBUG       @FilterChanges    Filter: Application, Distributor, Setup
DEBUG       @CommandExecute   Filter: Application, Distributor, Setup
DEBUG       @FilterFunction:  Filter: Application, Distributor, Setup
DEBUG       @FilterFunction:  Filter: Application, Distributor, Setup
DEBUG       @FilterFunction:  Filter: Application, Distributor, Setup

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

Ok, das war mir auch noch nicht bekannt.

The problem there is that CollectionViewSource.GetDefaultView(object) will always return the same ICollectionView instance for a given source, and this is what any ItemsControl extension will use when displaying that source.

Diese Änderung brachte den Erfolg:


//var view = CollectionViewSource.GetDefaultView(uc.Source);
var view = new CollectionViewSource { Source = uc.Source }.View;

Danke für eure Unterstützung.

16.835 Beiträge seit 2008
vor einem Jahr

wüsste warum das sinnvoller ist !?

Aus deinem switch (bool) solltest du ein if () else machen.
Und ich würde die View erst erzeugen und dann zuweisen:

Weil ein Switch für Mehrfach-Conditions mit einem End-Default gedacht ist, ein If jedoch für eine boolsche Condition, wie Du sie hier hast.
Es macht kein Sinn ein Bool in ein Switch zu werfen: ein bool hat nur zwei Werte (zumindest bei Nicht-Quanten-Computern).

Das widerstrebt jedem Standard, sieht man in keiner Sprache so.
Du machst es Dir selbst und Deinen Mitmenschen unangenehmer schwerer, wenn Du boolsche Conditions unnötigerweise über ein switch abfragst.

Weiteres Merkmal wäre, dass der Compiler ein Switch hier gar nicht so gut optimieren kann, wie eine einfach if-Condition.
Im Gegenteil: viele Switch-Konstellationen funktionieren nur, weil der Compiler daraus if-Zweige macht.
Fazit: es macht einfach wirklich null sinn und ist inhaltlich falsch.

4.939 Beiträge seit 2008
vor einem Jahr

Und beim 2. ist bei der Zuweisung der Filter ja noch nicht gesetzt (und ein evtl. vorhandenes ViewChanged-Ereignis o.ä. wüßte noch nichts vom Filter).
Dies sehe ich allgemein als Anti-Pattern, wenn man zuerst ein leeres Objekt erzeugt, zuweist und dann anschließend erst dessen Eigenschaften setzt, vergleichbar mit


list.Add(new X());
list[list.Count-1].Value = 42;

anstatt


var x = new X()
{
  Value = 42;
};
list.Add(x);

(gerade wenn man viel auch mit Multithreading zu tun hat, kennt man solche Fallstricke).

Aber irgendwie wußte ich wohl intuitiv, daß etwas an der View-Erzeugung nicht stimmt...

J
jbrown Themenstarter:in
18 Beiträge seit 2021
vor einem Jahr

Aus deinem switch (bool) solltest du ein if () else machen.
Und ich würde die View erst erzeugen und dann zuweisen:

@Abt

  1. Das hatte ich bereits als Verbesserung angenommen.

@Th69
2. Das wollte ich wissen und wirkt unter deinem Beispiel dann auch gut erklärt.


Aber irgendwie wußte ich wohl intuitiv, daß etwas an der View-Erzeugung nicht stimmt...

Darum hab ich mich an euch gewendet. Danke nochmal.