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
Das kenne ich nicht muss ich mal schauen ob das Auswirkungen hat Danke
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
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.
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 X
ein 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.
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.
😄 Achso ich versuche gerade die Stelle in den Docs zu finden.
Danke Abt,
Welches Dokument meinst du mit IIRC?
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.
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