Home > Net >  WPF Bind DataContext to item of an array based on int or enum
WPF Bind DataContext to item of an array based on int or enum

Time:09-13

Good afternoon everyone. To demonstrate what I'm after, let's say that I have the following classes:

public enum Field {FirstName, LastName, Address, City, State, Zipcode};

public class Item
{
   public Field Id {get; set;}
   public string Name {get; set;}

   public void Item(Field field, string name)
   {
      Id = field;
      Name = name;
   }
}

public class Items
{
   private List<Item> _Items;

   public void AddItem(Field field, string name)
   {
      _Items.Add(new Item(field, name));
   }

   public Item GetItem(Field field)
   {
      foreach(Item item in _Items)
      {
         if( item.Id == field ) return item;
      }
      return null;
   }
}

public Window SomeForm : Window
{
   private Items _Items;

   public SomeForm()
   {
      _Items = new Items();
      _Items.Add(Field.FirstName, "First Name");
      _Items.Add(Field.Address, "Address");

      DataContext = Items;

      InitializeComponent();
   }
}

And then in the XAML:

   <StackPanel Orientation="Horizontal">
      <Label DataContext="{Binding GetItem(Field.FirstName)}" Content="{Binding Name}" />
      <Label DataContext="{Binding GetItem(Field.Address)}" Content="{Binding Name}" />
   </StackPanel>

Ideally, I would like to do something where ControlField is an attached property:

   <StackPanel Orientation="Horizontal">
      <Label Style="{StaticResource MyLabel}" ControlField="{x:Static Field.FirstName}" />
      <Label Style="{StaticResource MyLabel}" ControlField="{x:Static Field.LastName}" />
   </StackPanel>

and then the binding of DataContext and label Content would occur in the MyLabel style.

I know GetItem(Field field) won't work (I think) but the following won't work (for several reasons) "{Binding DataContext[Field.FirstName]}".

I have worked with various things to no avail. I have kept my description somewhat high level so describe what I'm trying to accomplish. With this in mind, how I can go about this please? Thank you in advance.

CodePudding user response:

You can try it this way.

NuGet package CommunityToolkit.Mvvm

MainWindowViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApp2;

public partial class MainWindowViewModel : ObservableObject
{
    public MainWindowViewModel()
    {
        Items.Add(new Item(Field.FirstName, "First Name"));
        Items.Add(new Item(Field.LastName, "Last Name"));
    }

    [ObservableProperty]
    private ObservableCollection<Item> items = new ObservableCollection<Item>();
}

FieldToValueConverter.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace WpfApp2;

public class FieldToValueConverter : IValueConverter
{
    public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is IEnumerable<Item> items &&
            parameter is string fieldName &&
            Enum.TryParse<Field>(fieldName, out var field) is true)
        {
            return items.FirstOrDefault(x => x.Id == field)?.Name;
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    public MainWindowViewModel ViewModel { get; } = new();
}

MainWindow.xaml

<Window
    x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    x:Name="ThisWindow"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Window.Resources>
        <local:FieldToValueConverter x:Key="FieldToValueConverter" />
    </Window.Resources>
    <StackPanel Orientation="Horizontal">
        <Label Content="{Binding ElementName=ThisWindow, Path=ViewModel.Items, Converter={StaticResource FieldToValueConverter},ConverterParameter='FirstName'}"/>
        <Label Content="{Binding ElementName=ThisWindow, Path=ViewModel.Items, Converter={StaticResource FieldToValueConverter},ConverterParameter='LastName'}"/>
    </StackPanel>
</Window>

CodePudding user response:

There is no way to do what I would like to do. I cannot pass in an enum (for instance) for the array subscript. So, this is what I have done.

I added an attached property Field which takes an enum.

I made a class (WindowBase) that inherits from Window. My windows then inherit from WindowBase.

In WindowBase, in the constructor, I subscribe to each control's Load event and within that event I attach my own class to the control's datacontext which defines the label, text and other things.

Each control style contains the binding information to the class's property (Label.Contents binds to Label, TextBox.Text binds to Text, etc.)

Then, when laying out a form, each control in XAML then only needs to only define the control type, the style and the attached property enum. The styles handle the appropriate binding and WindowBase handles attaching various classes to each control.

I have written sample application which has 6 items in Field (FirstName, LastName, etc.), on the form I have 6 Labels and 6 TextBox's. This paradigm works very well for me.

So, this isn't an answer on how to use an enum for an array subscript in XAML but the workaround works great for me.

  • Related