Search code examples
c#wpfviewgriduniformgrid

WPF UniformGrid dynamic content


I have a uniformgrid which i want to dyanmically show contents/view. It can show either 1, 2, 4, or 6 contents. I set my uniformgrid row, col binding, and the content visibility binding. This does NOT work in the case of views/contents, but it DOES WORK in the case for rectangles. Why is that? How do i modify it to work with views/contents. Sample MainWindow.xaml code.....

<StackPanel>
    <ComboBox HorizontalAlignment="Left" Width="50" SelectedIndex="{Binding SelectedIndex}">
        <ComboBoxItem IsSelected="True" Content="1" />
        <ComboBoxItem Content="2" />
        <ComboBoxItem Content="4" />
        <ComboBoxItem Content="6" />
    </ComboBox>

    <!-- NOT CORRECT, SHOWS 6 ITEMS ALWAYS -->
    <UniformGrid Rows="{Binding ScreenRows}" Columns="{Binding ScreenCols}">
        <local:ScreenView Grid.Row="0" Grid.Column="0" Visibility="{Binding VisibleScreen[0]}" />
        <local:ScreenView Grid.Row="0" Grid.Column="1" Visibility="{Binding VisibleScreen[1]}" />
        <local:ScreenView Grid.Row="0" Grid.Column="2" Visibility="{Binding VisibleScreen[2]}" />
        <local:ScreenView Grid.Row="1" Grid.Column="0" Visibility="{Binding VisibleScreen[3]}" />
        <local:ScreenView Grid.Row="1" Grid.Column="1" Visibility="{Binding VisibleScreen[4]}" />
        <local:ScreenView Grid.Row="1" Grid.Column="2" Visibility="{Binding VisibleScreen[5]}" />
    </UniformGrid>

    <!-- OK, SHOWS 1,2,4, or 6 RECT DEPENDING ON SelectedIndex -->
    <UniformGrid Rows="{Binding ScreenRows}" Columns="{Binding ScreenCols}">
        <Rectangle Width="20" Height="20" Grid.Row="0" Grid.Column="0" Fill="Red" Visibility="{Binding VisibleScreen[0]}" />
        <Rectangle Width="20" Height="20" Grid.Row="0" Grid.Column="1" Fill="Orange" Visibility="{Binding VisibleScreen[1]}" />
        <Rectangle Width="20" Height="20" Grid.Row="0" Grid.Column="2" Fill="Yellow" Visibility="{Binding VisibleScreen[2]}" />
        <Rectangle Width="20" Height="20" Grid.Row="1" Grid.Column="0" Fill="Green" Visibility="{Binding VisibleScreen[3]}" />
        <Rectangle Width="20" Height="20" Grid.Row="1" Grid.Column="1" Fill="Blue" Visibility="{Binding VisibleScreen[4]}" />
        <Rectangle Width="20" Height="20" Grid.Row="1" Grid.Column="2" Fill="Violet" Visibility="{Binding VisibleScreen[5]}" />
    </UniformGrid>

</StackPanel>

Basically, i have a combobox. If I select 1, my ViewModel will dynamically change rows=1, cols=1, with 1st content Visible, and others collapse. It works in the case for Rectangles, but NOT views. Why is that? How to fix it so that it works for views?

Anyone curious, here is the code (Prism MVVM) in MainWindowViewModel

class MainWindowViewModel : BindableBase
{
    private const int SCREEN_MAX = 6;

    private int screenRows = 1;
    public int ScreenRows
    {
        get { return screenRows; }
        set { SetProperty(ref screenRows, value); }
    }

    private int screenCols = 1;
    public int ScreenCols
    {
        get { return screenCols; }
        set { SetProperty(ref screenCols, value); }
    }

    private Visibility[] visibleScreen = new Visibility[SCREEN_MAX];
    public Visibility[] VisibleScreen
    {
        get { return visibleScreen; }
        set { SetProperty(ref visibleScreen, value); }
    }

    private int selectedIndex;
    public int SelectedIndex
    {
        get { return selectedIndex; }
        set 
        { 
            SetProperty(ref selectedIndex, value);
            ChangeScreen(selectedIndex);                    
        }
    }

    private void ShowScreens(int num_screens)
    {
        // make all collapse
        for (int i = 0; i < SCREEN_MAX; i++)
            VisibleScreen[i] = Visibility.Collapsed;

        // show only X num screens
        for (int i = 0; i < num_screens; i++)
            VisibleScreen[i] = Visibility.Visible;

        RaisePropertyChanged("VisibleScreen");
    }

    public void ChangeScreen(int idx)
    {
        switch (idx)
        {
            case 0: ScreenRows = 1; ScreenCols = 1; ShowScreens(1); break;
            case 1: ScreenRows = 1; ScreenCols = 2; ShowScreens(2); break;
            case 2: ScreenRows = 2; ScreenCols = 2; ShowScreens(4); break;
            case 3: ScreenRows = 2; ScreenCols = 3; ShowScreens(6); break;
        }
    }
}

The ScreenView.xaml

<UserControl...>
    <!-- SOME HOW IT IS THIS DATACONTEXT CODE MESS UP THE UI -->
    <UserControl.DataContext>
        <local:ScreenViewModel x:Name="vm"/>
    </UserControl.DataContext>
    <StackPanel>
        <TextBlock>TEST</TextBlock>
    </StackPanel>
</UserControl>

Sample Output

enter image description here

As you can see from sample output, my combo selection is 1, so it should display 1 TEST, while 1 square is showing which is correct. But why should it show me 6 TEST? How to make it display 1 TEST?

Here's a corect output

enter image description here

Thanks!


Solution

  • Initial value of selectedIndex is 0 (default value of int field). But content of VisibleScreen array is not matching selectedIndex - you need to call ChangeScreen during initialization, e.g. like this:

    public MainWindowViewModel()
    {
        SelectedIndex = 0;
    }
    

    Btw,Grid.Row and Grid.Column have no effect in UniformGrid. UniformGrid arranges items in the order they are added

    Don't set DataContext inside UserControls.

    instead of Visibility[] create collection of ScreenViewModel of in MainWindowViewModel (ObservableCollection<ScreenViewModel>).

    Add bool IsVisible property in ScreenViewModel.

    Change ShowScreens method like this:

    private void ShowScreens(int num_screens)
    {
        for (int i = 0; i < SCREEN_MAX; i++)
            VisibleScreen[i].IsVisible = i < num_screens ? Visibility.Visible : Visibility.Collapsed;
    }
    

    Change the view:

    <ItemsControl ItemsSource="{Binding VisibleScreen}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                 <UniformGrid Rows="{Binding ScreenRows}" Columns="{Binding ScreenCols}"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <local:ScreenView Visibility="{Binding IsVisible}"/>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    ItemTemplate will create a local:ScreenView for each item in VisibleScreen collection AND assign it to DataContext of local:ScreenView