0
votes

I was hoping someone can help me figure this issue out. The combox seems to only list an item as individual characters when the binding source only has one value. If it is two or more, it lists the items properly.

Here's are two links with people experiencing similar issues. Link 1 Link 2

<DataTemplate>
  <ComboBox ItemsSource="{Binding 'Clusters'}" 
   SelectedItem="{Binding Path='TargetCluster', Mode=TwoWay}"
   Width="145"
/>

Here's the item source

$vCenters = @()
        Foreach ($vCenter in $VDIEnvironments) {
           $vCenter | 
              Add-Member -MemberType NoteProperty -Name TargetCluster -Value (
                 $clusters | ? VCName -like $vCenter.Name
              )[0].Name -Force
           $vCenter | 
              Add-Member -MemberType NoteProperty -Name Clusters -Value $(
                 $clusters | ? VCName -like $vCenter.Name
              ).Name -Force
           $vCenter | 
              Add-Member -MemberType NoteProperty -Name TargetDatastore -Value $(
                 $datastores | ? VCName -like $vCenter.Name | Sort-Object -Descending FreeSpaceMB
              )[0].Name -Force
           $vCenter | 
              Add-Member -MemberType NoteProperty -Name Datastores -Value $(
                 $datastores | ? VCName -like $vCenter.Name
              ).Name -Force 
           $vCenter | 
              Add-Member -MemberType NoteProperty -Name TargetPortgroup -Value (
                 $portgroups | ? VCName -like $vCenter.Name | Sort-Object -Descending NumPorts
              )[0].Name -Force
           $vCenter | 
              Add-Member -MemberType NoteProperty -Name Portgroups -Value $(
                 $portgroups | ? VCName -like $vCenter.Name
              ).Name -Force

           $vCenters += $vCenter
        }

Filling the datagridvie

            $SelectedVCenters = $VCenters | 
               Where-Object Env -like $WPFboxEnvironment.Text | 
               Where-Object Datastores -ne $Null
            $SelectedVCenters | ForEach-Object {
               $WPFboxSrcVCenter.Items.Add($_.Name)
               $WPFlistTgtVCenters.Items.Add($_)
               $WPFlistTgtVCenters.SelectedItems.Add($_)
            }    
2
This normally happens when you bind an ItemsControl.ItemsSource to a string. The ItemsControl usually accesses the collection bound to the ItemsSource by index, because it has to create a container for each data item (ItemContainerGenerator) in order to render the data as a Visual object. Since string implements an indexer like public char this[int index] { get; }, it is accessible by index like a collection or an array.BionicCode
Now, when binding a string to ItemsControl.ItemsSource, the internal ItemContainerGenerator treats the string value like a collection and accesses it by index. Because of the indexer, the string will return its underlying characters.BionicCode
Make sure you are always binding to a collection of string, but never to a string directly to avoid this behavior.BionicCode
Thanks for the reply. I somewhat understand. Do you mind giving me an example. I am fairly new to this.nickyung
Here's the link to the whole script pastebin.com/7A6yzLzCnickyung

2 Answers

0
votes

Thanks for the clarification. My colleague and I ended up adding an empty cluster to all vCenters to remedy the issue. Not the best solution but it works and solve two problems that we had.

$clusters = Get-Cluster | Select-Object Name, Uid

$clusters | ForEach-Object {
   $_ | Add-Member -MemberType NoteProperty -Name VCName -Value $_.Uid.split('@')[1].split(':')[0]
}

$uniqueVCs = $clusters | select VCName | sort VCName -Unique

foreach ($VC in $uniqueVCs) {
   $clusterplaceholder = [pscustomobject]@{
      'Name'   = "Select a cluster"
      'Uid'    = " "
      'VCName' = $VC.VCName
   }
   $clusters += $clusterplaceholder
}

$clusters = $clusters | sort VCName, Uid
-1
votes

This normally happens when you bind an ItemsControl.ItemsSource to a string. The ItemsControl internally accesses a copy of the collection bound to the ItemsSource by index, because it has to create a container for each data item (ItemContainerGenerator) in order to render the data as a Visual object.
Since string implements an indexer like

public char this[int index] { get; }

, it is accessible by index like a collection or an array.

Now, when binding a string to ItemsControl.ItemsSource, the string is copied to the ItemsControl.Items collection and passed on to the internal ItemContainerGenerator, which is responsible for the creation of the visual items that are finally rendered as a visual representation of the data. This ItemContainerGenerator is treats the string value like a collection (since string implements IEnumerable) and accesses it by index. Because of the implemented indexer, the string will return its underlying characters and then the generator creates a container for each. This is why the string looks split up.

Make sure you are always binding to a collection of string, but never to a string directly to avoid this behavior.

The view model that exposes the string value and a collection of strings for binding

class ViewModel : INotifyPropertyChanged
{
    private string stringValue;
    public string StringValue
    {
      get => this.stringValue;
      set
      {
        this.stringValue= value;
        OnPropertyChanged();
      }
    }

    private ObservableCollection<string> stringValues;
    public ObservableCollection<string> StringValues
    {
      get => this.stringValues;
      set
      {
        this.stringValues= value;
        OnPropertyChanged();
      }
    }
}

MainWindow.xaml where the DataContext is the ViewModel class

<!-- This ComboBox will display the single characters of the string value (each item is a character)-->
<ComboBox x:Name="comboBox" ItemsSource="{Binding StringValue}" />

<!-- This ComboBox will display the strings of the StringValues collection (each item is a complete string) -->
<ComboBox x:Name="comboBox" ItemsSource="{Binding StringValues}" />

The displayed items of a ComboBox (or ItemsControl in general) are actually containers. A container is the visual representation of the data and is complex like a UserControl. The container has Border, Background, Padding, Margin etc. It's a Visual that is composed of other Visuals (or controls). The string alone cannot be rendered like this (having a font, font color, background, etc).

Therefore the ItemsControl must create a visual container for each data object.
This is done by the ItemsControl.ItemsPanel which actually uses the ItemContainerGenerator to accomplish this. So, internally the ComboBox (or the ItemsControl.ItemsPanel) accesses the bound collection of ItemsControl.Items in order to create the container like this:

IItemContainerGenerator generator = this.ItemContainerGenerator;    
GeneratorPosition position = generator.GeneratorPositionFromIndex(0);

using (generator.StartAt(position, GeneratorDirection.Forward, true))    
{    
    DependencyObject container = generator.GenerateNext();    
    generator.PrepareItemContainer(container);    
}

As you can see the generator accesses the items by index. Under the hood the generator accesses a copy of ItemsControl.Items (ItemContainerGenerator.ItemsInternal) to retrieve the data that should be hosted by the container. This somehow (inside the generator) looks like:

object item = ItemsInternal[position];

Since string implements an indexer you can access a string also like an array:

var someText = "A String";
char firstCharacter = someText[0]; // References 'A' from "A String"

So when looking at the container generator code from above you now can now understand the effect the lines

GeneratorPosition position = generator.GeneratorPositionFromIndex(0);

and

generator.StartAt(position, GeneratorDirection.Forward, true)

have on a string where position is the actual item index: it retrieves character by character to map them to a container.

This is a simplified explanation of how the ItemsControl handles the source collection.