locked
WPF Binding to ItemSource in behind code and setting up columns and rows RRS feed

  • Question

  • Hallo

    I hit a point where we cannot continue and think we might need to go back to Windows Forms due to the complexity of what we trying to achieve with the WPF layout.

    We followed the MVVM patterns and our data is returned as a collection of objects in a BindableCollection. But we need to determine a specific row and column to insert the object properties, (also could be variable amount of objects).

    Each object represents a location for operators in a warehouse enviroment, (The object also holds the information on which row and columns this data needs to be inserted. (Meaning we will have x,y grid with OrderNr as the text value, and backColour of the cell based on another property in a single object..

    Done a small diagram which hopefully explains a bit better.

    And how the final layout should look, only different is we need to add the CellColour as a property.

    namespace WCS_WPF_FrontEnd.ViewModel

    { public class PigeonDataSetModelView { public BindableCollection <PigeonCellStatusModel> WallGrid { get; } public PigeonDataSetModelView() { var sqlTable = new DataTable(); var sqlDataTable = new tb_ProdPigeonStatusSql(); int rowCounter = 0; sqlTable = sqlDataTable.SelectAllRecords(); List<PigeonCellStatusModel> wallComplete = new List<PigeonCellStatusModel>(); foreach (DataRow dr in sqlTable.Rows) { var singleTempObject = new PigeonCellStatusModel(sqlTable.Rows[rowCounter]); wallComplete.Add(singleTempObject); rowCounter++; } WallGrid = new BindableCollection<PigeonCellStatusModel>(wallComplete); } } }

    Above the ModelViewClass that got all the objects in a BindableCollection.

    namespace WCS_WPF_FrontEnd
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }            
    
            private void SettingsLabel_MouseDown(object sender, MouseButtonEventArgs e)
            {
               var pigeonStuff = new PigeonDataSetModelView();
               WallGrid.ItemsSource = pigeonStuff.WallGrid;  
            }

    Code behind the XAML which populates the itemsource.

    But in windows form we could have created a foreach loop to place text in specific cells, eg.

    Data.Rows[0][1] = would put a value from the list. (pigeonStuff.WallGrid[0].OrderNumber)

    And similar for backcolour etc.

    I'm very lost so we get our data in a collection, how to manipulate the rows and columns and which is best approach or should we consider Winforms again?



    labjac

    • Moved by CoolDadTx Thursday, July 23, 2020 1:56 PM WPF related
    Thursday, July 23, 2020 10:06 AM

Answers

  • Hi,

    I guess there is a little tricky solution that should inspire you to resolve this issue, the idea is to make a listView of horizontal listViews, lets take a look to the code, we'll start with the Model, ViewModel then we'll see the xaml binding part: 

    public class CellColumns
    {
        public int Id { get; set; }
        public string Label { get; set; }
        public int Col { get; set; }
        public int Row { get; set; }
    }

    now lets make a view model which will contains an observable collection of lists, list of our cellColumns objects: 

    public class ViewModel
    {
        public ObservableCollection<List<CellColumns>> Items { get; set; } = new ObservableCollection<List<CellColumns>>(new List<List<CellColumns>>() {
            new List<CellColumns> { new CellColumns {  Col=0, Row=0, Label = "0,0"}, new CellColumns { Col=1, Row=0, Label = "1,0" }, new CellColumns { Col=2, Row=0, Label = "2,0" } },
            new List<CellColumns> { new CellColumns {  Col=0, Row=1, Label = "0,1" }, new CellColumns { Col=1, Row=1, Label = "1,1" }, new CellColumns { Col=2, Row=1, Label = "2,1" } }
        });
    }
    
    // you can also use sorted lists 
    public class ViewModel
    {
        public ObservableCollection<SortedList<int, CellColumns>> Items { get; set; } = new ObservableCollection<SortedList<int, CellColumns>>(new List<SortedList<int, CellColumns>>() {
            new SortedList<int, CellColumns> { { 0, new CellColumns { Col = 0, Row = 0, Label = "0,0" } }, { 1, new CellColumns { Col = 1, Row = 0, Label = "1,0" } }, { 2, new CellColumns { Col = 2, Row = 0, Label = "2,0" } } },
            new SortedList<int, CellColumns> { { 0, new CellColumns { Col = 0, Row = 1, Label = "0,1" } } , { 1, new CellColumns { Col = 1, Row = 1, Label = "1,1" } } , { 2, new CellColumns { Col = 2, Row = 1, Label = "2,1" } } }
        });
    } 



    the next step is to have a ListView which have a horizontal ListView as an ItemTemlate:

        <Window.DataContext>
            <local:ViewModel></local:ViewModel>
        </Window.DataContext>
        <Grid>
            <ListView ItemsSource="{Binding Items}" >
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ListView ItemsSource="{Binding}" > <!--here the item is bound to the lists in the observable collection-->
                            <ListView.ItemTemplate>
                                <DataTemplate DataType="{x:Type local:CellColumns}">
                                    <ListBoxItem Content="{Binding Label}"/>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                            <ListView.ItemsPanel> <!--here we use WrapPanel as an itemPanel to change the orientation-->
                                <ItemsPanelTemplate>
                                    <WrapPanel Width="{Binding (FrameworkElement.ActualWidth), 
                                        RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
                                        ItemWidth="{Binding (ListView.View).ItemWidth, 
                                        RelativeSource={RelativeSource AncestorType=ListView}}"
                                        MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
                                        ItemHeight="{Binding (ListView.View).ItemHeight, 
                                        RelativeSource={RelativeSource AncestorType=ListView}}" />
                                </ItemsPanelTemplate>
                            </ListView.ItemsPanel>
                        </ListView>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>


    horizontal alignement snippet source: https://stackoverflow.com/questions/1041551/wpf-listview-with-horizontal-arrangement-of-items

    this is just an idea that I'hope will inspire you.

    Best Regards,

    Mouad.

    • Edited by Cherkaoui.Mouad Thursday, July 23, 2020 11:33 AM
    • Marked as answer by labjac Thursday, July 23, 2020 4:27 PM
    Thursday, July 23, 2020 11:21 AM

All replies

  • Hi,

    I guess there is a little tricky solution that should inspire you to resolve this issue, the idea is to make a listView of horizontal listViews, lets take a look to the code, we'll start with the Model, ViewModel then we'll see the xaml binding part: 

    public class CellColumns
    {
        public int Id { get; set; }
        public string Label { get; set; }
        public int Col { get; set; }
        public int Row { get; set; }
    }

    now lets make a view model which will contains an observable collection of lists, list of our cellColumns objects: 

    public class ViewModel
    {
        public ObservableCollection<List<CellColumns>> Items { get; set; } = new ObservableCollection<List<CellColumns>>(new List<List<CellColumns>>() {
            new List<CellColumns> { new CellColumns {  Col=0, Row=0, Label = "0,0"}, new CellColumns { Col=1, Row=0, Label = "1,0" }, new CellColumns { Col=2, Row=0, Label = "2,0" } },
            new List<CellColumns> { new CellColumns {  Col=0, Row=1, Label = "0,1" }, new CellColumns { Col=1, Row=1, Label = "1,1" }, new CellColumns { Col=2, Row=1, Label = "2,1" } }
        });
    }
    
    // you can also use sorted lists 
    public class ViewModel
    {
        public ObservableCollection<SortedList<int, CellColumns>> Items { get; set; } = new ObservableCollection<SortedList<int, CellColumns>>(new List<SortedList<int, CellColumns>>() {
            new SortedList<int, CellColumns> { { 0, new CellColumns { Col = 0, Row = 0, Label = "0,0" } }, { 1, new CellColumns { Col = 1, Row = 0, Label = "1,0" } }, { 2, new CellColumns { Col = 2, Row = 0, Label = "2,0" } } },
            new SortedList<int, CellColumns> { { 0, new CellColumns { Col = 0, Row = 1, Label = "0,1" } } , { 1, new CellColumns { Col = 1, Row = 1, Label = "1,1" } } , { 2, new CellColumns { Col = 2, Row = 1, Label = "2,1" } } }
        });
    } 



    the next step is to have a ListView which have a horizontal ListView as an ItemTemlate:

        <Window.DataContext>
            <local:ViewModel></local:ViewModel>
        </Window.DataContext>
        <Grid>
            <ListView ItemsSource="{Binding Items}" >
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ListView ItemsSource="{Binding}" > <!--here the item is bound to the lists in the observable collection-->
                            <ListView.ItemTemplate>
                                <DataTemplate DataType="{x:Type local:CellColumns}">
                                    <ListBoxItem Content="{Binding Label}"/>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                            <ListView.ItemsPanel> <!--here we use WrapPanel as an itemPanel to change the orientation-->
                                <ItemsPanelTemplate>
                                    <WrapPanel Width="{Binding (FrameworkElement.ActualWidth), 
                                        RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
                                        ItemWidth="{Binding (ListView.View).ItemWidth, 
                                        RelativeSource={RelativeSource AncestorType=ListView}}"
                                        MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
                                        ItemHeight="{Binding (ListView.View).ItemHeight, 
                                        RelativeSource={RelativeSource AncestorType=ListView}}" />
                                </ItemsPanelTemplate>
                            </ListView.ItemsPanel>
                        </ListView>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>


    horizontal alignement snippet source: https://stackoverflow.com/questions/1041551/wpf-listview-with-horizontal-arrangement-of-items

    this is just an idea that I'hope will inspire you.

    Best Regards,

    Mouad.

    • Edited by Cherkaoui.Mouad Thursday, July 23, 2020 11:33 AM
    • Marked as answer by labjac Thursday, July 23, 2020 4:27 PM
    Thursday, July 23, 2020 11:21 AM
  • Please post questions related to WPF in their forums at Microsoft Q&A.

    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, July 23, 2020 1:56 PM
  • Thank Mouad

    Appreciate the assistance, now I got some idea on how to proceed.

    Michael that Q&A from the Microsoft Forum is not even close to as great as this forum, I've posted on the WPF forum as you told me, but it's not great.

    Mouad I will mark this as answered, but might need to ask some more assistance.


    labjac

    Thursday, July 23, 2020 4:27 PM
  • It's a pleasure! Welcome.

    I' m still thinking of better solution, if any I'll post it here.

    Best regards,

    Mouad.

     
    Thursday, July 23, 2020 4:34 PM
  • Hi,

    Here is a better solution that I just found on the forum, lets go back to code, consider this ItemWrapper<TItem>:

    public class ItemWrapper<TItem>
    {
        public TItem Item { get; set; } // still have the raw item value
        public int Col { get; set; }
        public int Row { get; set; }
    }

    in our VM we will use a simple ObservableCollection<ItemWrapper<DemoItem>> here is our demo item representing the wrapped object:

    public class DemoItem
    {
        public int Id { get; set; }
        public string Label { get; set; }
    }
    
    public class DemoItemWrapper:ItemWrapper<DemoItem>
    {
    
    }

    and the view model: 

    public class ViewModel
    {
        public static ObservableCollection<ItemWrapper<DemoItem>> WrappedItems { get; set; } = new ObservableCollection<ItemWrapper<DemoItem>>(new List<ItemWrapper<DemoItem>>() {
            new ItemWrapper<DemoItem> { Col = 0, Row = 0, Item = new DemoItem {Label = "0,0" } },
            new ItemWrapper<DemoItem> { Col = 1, Row = 0, Item = new DemoItem {Label = "1,0" } },
            new ItemWrapper<DemoItem> { Col = 2, Row = 0, Item = new DemoItem {Label = "2,0" } },
            new ItemWrapper<DemoItem> { Col = 0, Row = 1, Item = new DemoItem {Label = "0,1" } },
            new ItemWrapper<DemoItem> { Col = 1, Row = 1, Item = new DemoItem {Label = "1,1" }},
            new ItemWrapper<DemoItem> { Col = 2, Row = 1, Item = new DemoItem {Label = "2,1" } }
        });
    }

    now here is the interesting part, we will use a UniformGrid as ItemsPanelTemplate of our list view: 

    <ListView ItemsSource="{Binding WrappedItems}">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid IsItemsHost="True">
                </UniformGrid>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Grid.Column="{Binding Path=Col}" Grid.Row="{Binding Path=Row}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Background="Azure" Text="{Binding Path=Row}"></TextBlock>
                        <TextBlock Background="Azure" Text="/"></TextBlock>
                        <TextBlock Background="Azure" Text="{Binding Path=Col}"></TextBlock>
                    </StackPanel>
                    <TextBlock Background="Azure" Text="{Binding Path=Text}"></TextBlock>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    
    

    here is the source thread of the solution "using UniformGrid as ItemsPanelTemplate" :

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/4fffc6b0-8c34-4f19-83f4-e1ca3e4bfd67/how-to-bind-to-gridrow-and-gridcolumn

    Best regards,

    Mouad.

    Thursday, July 23, 2020 10:13 PM
  • Thanks Mouad

    This is very helpful, I will give this a test during the week, really appreciate the effort and assistance.

    I think I got a better idea on how to proceed now, always a battle with something new.. :-)


    labjac

    Monday, July 27, 2020 2:55 AM
  • Welcome! It was a pleasure!

    Mouad.

    Monday, July 27, 2020 7:27 AM