Thursday, April 21, 2022

Access a dictionary's keyvaluepair by position

I have some generic code that takes a dictionary with unknown generic types and returns all KeyValuePairs with keys that match a specific condition. The problem I have is that I cannot iterate through the collection of KeyValuePairs because that would require code like...

For Each o As KeyValuePair(Of T1, T2) In IDict

but I don't know T1 or T2 at design time. Similarly the following lines throw invalid cast errors.

For Each o As KeyValuePair(Of Object, Object) In IDict

For Each o As KeyValuePair(Of Object, Object) In IDict.Cast(Of KeyValuePair(Of Object, Object))

There is the option to iterate over a DictionaryEntry collection which does not require me to know the generic types at design time

For Each o As DictionaryEntry In IDict

Now I can examine the keys and values but I can't find a way to convert the DictionaryEntry back to the equivalent KeyValuePair. But I can track the index of the current DictionaryEntry and use IDict.AsQueryable(index) to get the matching KeyValuePair.

Here's an example in Visual Basic and WPF. Just click the [Find Y] button.

Start a Visual Basic WPF application using .Net Framework.

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GetKeyValuePair"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <RoutedCommand x:Key="FindYCommand"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource FindYCommand}" Executed="FindY_Executed"/>
    </Window.CommandBindings>
    <StackPanel Orientation="Horizontal" Height="22">
        <ComboBox ItemsSource="{Binding TheDictionary}"
                  SelectedItem="{Binding TheSelection}"
                  DisplayMemberPath="Key" Width="100"/>
        <Button Content="Find Y" Command="{StaticResource FindYCommand}"/>
    </StackPanel>
</Window>

-------------------------------------------------------------------------------

Imports System.ComponentModel
Imports System.Runtime.CompilerServices
 
Class MainWindow
    Implements INotifyPropertyChanged
 
    Private _TheDictionary As New Dictionary(Of String, Boolean?) From {{"MAYBE", Nothing}, {"YES", True}, {"NO", False}}
    Public ReadOnly Property TheDictionary
        Get
            Return _TheDictionary
        End Get
    End Property
 
    Private _TheSelection As KeyValuePair(Of String, Boolean?)
 
    Public Property TheSelection As KeyValuePair(Of String, Boolean?)
        Get
            Return _TheSelection
        End Get
        Set(value As KeyValuePair(Of String, Boolean?))
            SetProperty(_TheSelection, value)
        End Set
    End Property
 
    Private Sub FindY_Executed(sender As Object, e As ExecutedRoutedEventArgs)
        TheSelection = FindY(TheDictionary)
    End Sub
 
    Private Function FindY(IDict As IDictionary) As Object
        ' Let's assume that we don't know TheDictionary generic types.
        ' We can't iterate through the KeyValuePairs because requires us to know the types at design time
        ' Find the first dictionary entry with a key that contains Y.
        ' AsQueryable returns a list of KeyValuePairs
 
        Dim index As Integer = 0
 
        If IDict.Count = 0 Then Return Nothing
        For Each o As DictionaryEntry In IDict
            If o.Key.ToString.Contains("Y") Then
                Return IDict.AsQueryable(index)
            End If
            index += 1
        Next
        Return Nothing
    End Function
 
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Public Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional PropertyName As String = "") As Boolean
        If Object.Equals(storage, value) Then Return False
        storage = value
        NotifyPropertyChanged(PropertyName)
        Return True
    End Function
    Public Sub NotifyPropertyChanged(PropertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
    End Sub
End Class
 




 

Thursday, March 31, 2022

Setting Window style in XAML

If you use styles (you DO use styles, right?) you may find that you cannot set the style of a Window or User Control to a static resource in XAML. Consider the following resource dictionary which contains the GreenWindowStyle style.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="Window" x:Key="GreenWindowStyle">
        <Setter Property="Background" Value="Lime"/>
    </Style>
</ResourceDictionary>

I'd like to set my Window's style to GreenWindowStyle. The Window tag has a style attribute so I should be able to use it like this.

<Window x:Class="WindowStyle.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WindowStyle"
        mc:Ignorable="d"
        Style="{StaticResource GreenWindowStyle}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary Source="/Styles.xaml"/>
    </Window.Resources>
    <Grid>
 
    </Grid>
</Window>

 But I get an exception that InitializeComponent cannot find GreenWindowStyle. 




The problem, of course, is that the reference to GreenWindowStyle is parsed before the reference to Styles.xaml. How can we put it after? Try this...

<Window x:Class="WindowStyle.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WindowStyle"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary Source="/Styles.xaml"/>
    </Window.Resources>
    <Window.Style>
        <Binding Source="{StaticResource GreenWindowStyle}"/>
    </Window.Style
>
    <Grid>
 
    </Grid>
</Window>

Now the reference to GreenWindowStyle is after the reference to Styles.xaml so we get this beautiful window!




Thursday, February 17, 2022

XamDataGrid can't bind same property twice

I came across this bug when I bound the same property twice in a XamDataGrid. I see no reason I shouldn't be able to do this. Not only did the grid throw an exception, but the exception was the wrong one. I wasted a lot of time before I realized what the real problem was. Here's a simple demonstration.

Start Visual Studio and create a new C#, WPF Framework Application. Call it AnotherXamDataGridBug. Set the XAML and Code behind to this.

<Window x:Class="AnotherXamDataGridBug.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AnotherXamDataGridBug"
        xmlns:igDP="http://infragistics.com/DataPresenter"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <igDP:XamDataGrid DataSource="{Binding Items}" GroupByAreaLocation="None">
            <igDP:XamDataGrid.FieldLayoutSettings>
                <igDP:FieldLayoutSettings AutoGenerateFields="False"/>
            </igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout>
                    <igDP:FieldLayout.Fields>
                        <igDP:TextField Label="Name 1" Name="Name"/>
                        <igDP:TextField Label="Name 2" Name="Name"/>
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>
    </Grid>
</Window>
 
--------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Windows;
 
namespace AnotherXamDataGridBug
{
    public partial class MainWindow : Window
    {
        public class cItem
        {
            public string Name { get; set; }
        }
 
        public List<cItem> Items { get; set; }
        public MainWindow()
        {
            Items = new List<cItem>() { new cItem() { Name = "BOB" }};
            try
            {
                InitializeComponent();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.InnerException.Message);
            }
        }
    }
}

Now run it and you will see an exception being thrown by the grid.


We can see perfectly well that there is a public property called Name so this exception is wrong. Firstly, I should be able to bind to the same field twice - I can bind two text boxes to a property, why can't I bind two data grid columns? Secondly, the "field" (should say property) is there so I would expect a better error message.

According to Infragistics you can get around the problem by using AlternateBinding like this.

<igDP:TextField Label="Name 1" AlternateBinding="{Binding Name}"/>
<igDP:TextField Label="Name 2" AlternateBinding="{Binding Name}"/>

Still smells like a bug.