Thursday, April 27, 2023

LINQ OfType - Converting an IEnumerable to IEnumerable<Type>

Some collections such as ItemCollection implement IEnumerable but do not implement IEnumerable<Type>, DataTable.Rows is another example. If you want to use LINQ extensions such as Any, All, First, etc. you can use Cast<Type> or OfType<Type> to convert them to an IEnumerable<Type>. Then you can use the LINQ extensions.

Here is an example. Start a new Visual Basic project called OfType. Make it a WPF C# application.

We will create a context menu, then use LINQ to see if it contains a particular menu item when the menu is opened. Here is the XAML and code behind without the conversion to IEnumerable<Type>. You can see it will not build.

<Window x:Class="OfType.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:OfType"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        ContextMenuOpening="Window_ContextMenuOpening"
        Title="MainWindow" Height="450" Width="800">
    <Window.ContextMenu>
        <ContextMenu>
            <MenuItem Header="One"/>
            <MenuItem Header="Two"/>
            <MenuItem Header="Three"/>
        </ContextMenu>
    </Window.ContextMenu>
</Window>


using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace OfType
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void Window_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            Window w = sender as Window;
            ContextMenu cm = w.ContextMenu as ContextMenu;
            Boolean Exists = false;
 
            Exists = cm.Items.Any(mi => mi.Header.ToString() == "Two");
            Console.WriteLine("Menu item 'Two' does " + (Exists ? "":"not ") + "exist");
        }
    }
}

There are two ways to fix the problem. Cast<Type> and OfType<Type>. The Cast method will raise an InvalidCast exception if any of the members of the collection cannot be cast. The OfType method will only return the members that can be cast. I will use OfType. Replace the line starting with Exists.

Exists = cm.Items.OfType<MenuItem>().Any(mi => mi.Header.ToString() == "Two");

You can now run the application and see the output in the Immediate window.

Menu item 'Two' does exist

Tuesday, April 4, 2023

ValueSource - how did that dependency property get its value?

I have a custom control that binds to a complex object and binds itself to various properties in the object. One of my developers needed to be able to override one of these bindings. I wanted my custom control to be able to detect if the developer had explicitly set a Text attribute and not overwrite it with its own binding.

I found the ValueSource structure which is the subject of this interesting page.

You can use GetValueSource to get the ValueSource for a dependency property. If you also combine this with GetBindingExpression which gets the actual Binding Expression, you can figure out a lot about a DependencyProperty. In particular, you can tell if the developer explicitly set a value for a DependencyProperty.

Here's a quick demonstration. Start a new WPF C# project called ValueSource. Set MainWindow xaml and vb to this...

<Window x:Class="ValueSource.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:ValueSource"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Window.Resources>
        <Style TargetType="TextBlock" x:Key="TestBlockStyle">
            <Setter Property="Text" Value="NamedStyle"/>
        </Style>
        <Style TargetType="TextBlock" x:Key="StyleWithTrigger">
            <Style.Triggers>
                <Trigger Property="Visibility" Value="Visible">
                    <Setter Property="Text" Value="StyleWithTrigger"/>
                </Trigger>
            </Style.Triggers>
        </Style>
       
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <TextBlock Loaded="TextBlock_Loaded"/>
        <TextBlock Text="Literal" Loaded="TextBlock_Loaded"/>
        <TextBlock Text="{Binding TextBlock3_Text}" Loaded="TextBlock_Loaded"/>
        <TextBlock Style="{StaticResource TestBlockStyle}" Loaded="TextBlock_Loaded"/>
        <TextBlock Style="{StaticResource StyleWithTrigger}" Loaded="TextBlock_Loaded"/>
    </StackPanel>
</Window>

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

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
 
namespace ValueSource
{
    public partial class MainWindow : Window
    {
        public String TextBlock3_Text { get { return "BoundText"; } }
 
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void TextBlock_Loaded(object sender, RoutedEventArgs e)
        {
            TextBlock tb = (TextBlock)sender;
            System.Windows.ValueSource vs = DependencyPropertyHelper.GetValueSource(tb, TextBlock.TextProperty);
            BindingExpression be = tb.GetBindingExpression(TextBlock.TextProperty);
            Console.WriteLine($"TextBlock '{tb.Text}' ValueSource is {vs.BaseValueSource.ToString()} and is {(be == null?"not ":"")}bound");
        }
    }
}

We are defining several TextBlock controls and initializing their Text property in different ways. Then we're using GetValueSource and GetBindingExpression to tell us how the Text property was initialized. The output in the Immediate window looks like this...

TextBlock '' ValueSource is Default and is not bound
TextBlock 'Literal' ValueSource is Local and is not bound
TextBlock 'BoundText' ValueSource is Local and is bound
TextBlock 'NamedStyle' ValueSource is Style and is not bound
TextBlock 'StyleWithTrigger' ValueSource is StyleTrigger and is not bound

In my custom control I simply look for a BaseValueSource of Default and I can add my binding. Any other value means the developer has explicitly set a value for Text so I should not override it with my binding.

Here's a full list of possible values for BaseValueSource.


Another way to do this is to use ReadLocalValue. This returns UnsetValue, a Binding Expression, or a value. This technique was suggested by ChatGPT. If you change TextBlock_Loaded to the following...

private void TextBlock_Loaded(object sender, RoutedEventArgs e)
{
    TextBlock tb = (TextBlock)sender;
    object localValue = tb.ReadLocalValue(TextBlock.TextProperty);
    Console.WriteLine($"TextBlock '{tb.Text}' LocalValue is {localValue.ToString()}");
}

The output in the Immediate window now looks like this...

TextBlock '' LocalValue is {DependencyProperty.UnsetValue}
TextBlock 'Literal' LocalValue is Literal
TextBlock 'BoundText' LocalValue is System.Windows.Data.BindingExpression
TextBlock 'NamedStyle' LocalValue is {DependencyProperty.UnsetValue}
TextBlock 'StyleWithTrigger' LocalValue is {DependencyProperty.UnsetValue}

My custom control can apply its own binding if LocalValue is UnsetValue.