Laden...
L
lutzeslife
myCSharp.de - Member
40
Themen
158
Beiträge
Letzte Aktivität
vor 17 Stunden
Dabei seit
25.07.2012
Beruf
Softwareentwickler
Herkunft
Dresden
Erstellt vor 18 Stunden

Hallo Community,

ich versuche eine Art mehrstufige Sortierung bei einer ListBox in WPF hinzubekommen. Dazu folgendes Beispiel (ich verichte auf die ganzen PropertyChanges etc. aufrufe der Übersichtlichkeit halber)

public class Data
{
 public string Name,
 public string Category
 public int Index // das ist der Index aus der Quelle von dem die Daten kommen, dieser kann mehrfach herkommen
 public int Offset
}

Ich habe eine ObservableCollection<Data> die habe ich an eine CollectionViewSource gebunden und die wieder rum an die Listbox.  Über

<PropertyGroupDescription Converter="{StaticResource dataToGroupConverter}" CustomSort="{StaticResource GroupComparer}"/> 

Habe ich einen Converter der die Category zu einer GruppenID macht und der Comparer sortiert die Gruppen dann entsprechend nach. Es gibt noch TemplateSelektoren die ja nach GruppenID ein anderen Style laden aber das spielt gar nicht so eine Rolle.

Vereinfacht wird dann im ListBox folgendes angezeigt:

- Category 1
	- "[Name]" // Instance OBJ 1 hat dann Index 0
	- "[Name]" // Instance OBJ 2 hat Index 3
- Category 2
	- "[Name]" // Instance OBJ 3 hat dann Index 1
	- "[Name]" // Instance OBJ 4 hat dann Index 2
- Category 3
	- "[Name]" // Instance OBJ 5 hat dann Index 1
	- "[Name]" // Instance OBJ 6 hat dann Index 3

Im Moment habe ich eine Problem mit der Sortierung innerhalb einer Gruppe. Die ist aktuell der Reihenfolge wie sie hinzugefügt wurde zur Collection.  Was ich jetzt bräuchte das ist eine Arte zweite Sortierung in Abhängigkeit der ersten Sortierung. D.h.

Im ersten Schritt sortiere ich alle Mitglieder  einer Kategory nach dem Index dann kommt das Beispiel oberen aus. Nun habe ich eine Offset Wert der sagt das mein Position verschoben werden kann (am Beispiel)

// Nach erster Sortierung

- "Kategory 1"
	- "Wert 1" 	// Obj: Name = "Wert 1", Category = "Kategory 1", Index  = 0, Offset = 0
	- "Wert 1" 	// Obj: Name = "Wert 2", Category = "Kategory 1", Index  = 3, Offset = 0
	- "Wert 2"  // Obj: Name = "Wert 3", Category = "Kategory 1", Index  = 4, Offset = -2
	
// Nach zweiter Sortierung

- "Kategory 1"
	- "Wert 2"  // Obj: Name = "Wert 3", Category = "Kategory 1", Index  = 4, Offset = -2
	- "Wert 1" 	// Obj: Name = "Wert 1", Category = "Kategory 1", Index  = 0, Offset = 0
	- "Wert 1" 	// Obj: Name = "Wert 2", Category = "Kategory 1", Index  = 3, Offset = 0

Ich verschiebe also visuell durch den Offset Wert meine Position innerhalb der Gruppe.

Meine erste Idee war die SortDescriptions bzw. CollectionViewSource im Code-behind zu ListCollectionView zu casten und CustomSort zu nutzen. Das wird aber auf alle Elemente der ItemsSource angewandt. Da kenn ich ich als den "GruppenIndex"  noch nicht. Eine weitere Idee war eine zweite sortierte Liste mitlaufen zu lassen aus denen ich dann die "GruppenIndexes" berechnen und dann den Offset dazunehme. Aber dann habe ich eine weitere Datenstruktur die ich pflegen muss.

Was irgendwie fehlt das man einen weiteren Comparer für die Items innerhalb einer Gruppe anhängen kann.

Vielleicht hat einer eine Idee?

Grüße

Erstellt vor 19 Stunden

Das kenne ich nicht muss ich mal schauen ob das Auswirkungen hat Danke

Erstellt vor 5 Tagen

Hallo Community,

ich versuche mich nach Jahren wieder in WPF reinzuarbeiten, habe aber das Gefühl alles vergessen zu haben. Ich versuche ein CustomControl von einer ComboBox zu bauen das neben der ComboBox einen ResetButton anzeigen zu hat.

 public class ResetableComboBox : ComboBox
    {
        // STATIC PROPERTIES
        public static readonly RoutedEvent ResetEvent;

        // STATIC CONSTRUCTORS
        static ResetableComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ResetableComboBox), new FrameworkPropertyMetadata(typeof(ResetableComboBox)));

            ResetEvent = EventManager.RegisterRoutedEvent(nameof(Reset), RoutingStrategy.Bubble, typeof(RoutedEventArgs), typeof(ResetableComboBox));
        }

        // FIELDS
        private Button _resetButton;


        // METHODS
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _resetButton = Template?.FindName("resetButton", this) as Button;

            if (_resetButton != null)
            {
                _resetButton.Click += ResetButton_Click;
            }
        }

        private void ResetButton_Click(object sender, RoutedEventArgs e) => OnReset();

        protected virtual void OnReset() => RaiseEvent(new RoutedEventArgs(ResetEvent));

        // EVENTHANDLER

        public event RoutedEventHandler Reset
        {
            add { AddHandler(ResetEvent, value); }
            remove { RemoveHandler(ResetEvent, value); }
        }
    }
    }

Der C# Code klappt auch soweit wenn ich den ResetButton drücke dann wird auch das Event geworfen.  In der Generic.xaml habe ich dann in den beiden ComboxTemplates (habe mir per VS > WPF Designer > ComboBox Selektion > Edit Template das komplette Template ausgeben lassen und an mein neues Control angepasst)

<ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type customControls:ResetableComboBox}">
        <Grid x:Name="templateRoot" SnapsToDevicePixels="true">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
                <ColumnDefinition MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" Width="0"/>
            </Grid.ColumnDefinitions>
            <Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
                <Themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
                    <Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
                        <ScrollViewer x:Name="DropDownScrollViewer">
                            <Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
                                <Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
                                    <Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
                                </Canvas>
                                <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </Grid>
                        </ScrollViewer>
                    </Border>
                </Themes:SystemDropShadowChrome>
            </Popup>
            <Button x:Name="resetButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="2" Style="{StaticResource ComboBoxResetButton}" Margin="-1,0,0,0" /> 
            <ToggleButton x:Name="toggleButton" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ComboBoxToggleButton}"/>
            <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Content="{TemplateBinding SelectionBoxItem}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </Grid>

Wenn ich nun das ganze in einem Window einbinden klappt auch alles. Meine Frage wäre jetzt, müsste es nicht möglich sein auf Window Ebene ein neuen Style anzulegen für dieses Control.

<Style TargetType="{x:Type customControls:ResetableComboBox}">
            <Setter Property="Background" Value="AliceBlue"/>
        </Style>

Aber das ändert die Farbe nicht. Im WPF Buch von Huber wird im Kapitel "CustomControls" auf Window Ebene  im Style auch das Template überschrieben, ich war aber der Meinung dass das auch so gehen müsste.

Grüße

Daniel

Erstellt vor einem Jahr
 internal class Rectangle
    {
        public int X { get; set; }
    }

    internal class Control
    {
        private Rectangle _bounds;

        public Control()
        {
            _bounds = new Rectangle { X = 5 };
        }

        public int X => _bounds.X;
    }

    internal class Button : Control
    { 
    
    }

Habe es mal kurz durchgespielt

  static void Main(string[] args)
        {
            var button = new Button();

            Console.WriteLine($"X (OLD): {button.X}");

            // Basisklasse öffentlich, Rectangle Klasse öffentlicht

            var bounds = (Rectangle)typeof(Control)
                                .GetField("_bounds", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                                .GetValue(button);
            bounds.X = 6;

            Console.WriteLine($"X (NEW): {button.X}");


            //  Basisklasse nicht öffentlich, Rectangle Klasse nicht öffentlicht

            var boundsOBJ = button
                            .GetType()
                            .BaseType // Rekursiv suchen ggf.
                            .GetField("_bounds", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                            .GetValue(button);

            // Autoproperties bekommen einen autogenierten Namen der __BackingField enthält
            boundsOBJ.GetType().GetField("<X>k__BackingField", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(boundsOBJ, 20);

            Console.WriteLine($"X (NEW) WAY 2: {button.X}");

            Console.ReadLine();
            
        }

Im wesentlichen  kannst du alles was öffentlich ist auch nutzen für Reflection ansonsten muss man es eben generisch machen. Mit Reflection bekommst du fast alles geliefert und kannst auch Änderungen vornehmen (mit Ausnahmen).  Eventuell falls du Reflection häufig brauchst würde ich über Caching der typen etc. nachdenken. Es gibt einige Blogbeiträge zum Thema Performance bei Reflection.

Erstellt vor einem Jahr
public ref int X => ref _bounds.X;

Wenn du weißt dass die Property nur ein Fascade für ein zugrundeliegendes Objekt ist, kannst du via Reflection auch die das Object _bounds einfach direct holen und prüfen ob deren X Property ein Setter hat. Ggf. wenn es weitere Objekte gibt hangelst du dich durch. Sollte Xein Auto-Property sein erzeugt der Compiler automatisch ein Feld nach einem bestimmten Namensschema (das kannst du dir i Debugger auslesen). Das Feld solltest du via Reflection schreiben können.

Erstellt vor 4 Jahren

Erstmal ein Dankschön an alle die geantwortet haben. Die Variante mit Exceptionhandling sowie die Sache mit using, habe ich auch jetzt auf dem Schirm. Gerade das Kommentar von Toub finde ich dort sehr hilfreich.

Erstellt vor 4 Jahren

😄 Achso ich versuche gerade die Stelle in den Docs zu finden.

Erstellt vor 4 Jahren

Danke Abt,

Welches Dokument meinst du mit IIRC?

Erstellt vor 4 Jahren

Hallo Community,

ich habe eine Frage zum Thema Task bzw. async/await.


        static async void Main(string[] args)
        {
            await DoSomthingAsync();

            await DoSomthingAsync2();
        }


        static async Task DoSomthingAsync()
        {
            await Calling();
        }

        static Task DoSomthingAsync2()
        {
            return Calling();
        }


        static Task Calling() => Task.Delay(1);

Meine Frage ist ob es einen Unterschied macht, ob ich auf den Task awaite wie in DoSomthingAsync oder den Task durchroute wenn der Aufrufer sowieso ein await macht?

Das einzige was mir auffällt ist dass wenn ich schon ein await mache:


        static async Task DoSomthingAsync3()
        {
            await Calling();
            return Calling();
        }

Das ich dann den letzten Aufruf durchrouten kann.

Erstellt vor 5 Jahren

Cool jetzt funktioniert es wieder und manchmal kann ein Zeichen entscheident sein! Vielen Dank für die Hilfe! 👍

P.S. Gerade noch gefundeb Visual Studio Version Ranges Demystified