1

I've got a WPF Grid and would like to move rows up or down depending on the user's input. This is what I've tried so far (an example for when the user decides to move an element up):

RowDefinition currentRow = fieldsGrid.RowDefinitions[currentIndex];
fieldsGrid.RowDefinitions.Remove(currentRow);
fieldsGrid.RowDefinitions.Insert(currentIndex - 1, currentRow);

Am I doing something wrong? As the UI remains the same using this approach.

Dot NET
  • 4,891
  • 13
  • 55
  • 98
  • 1
    `I've got a WPF Grid and would like to move rows up or down depending on the user's input` - What do you want that for? please clarify your intention because probably there is a much better and simpler way to do what you need. – Federico Berasategui Feb 26 '13 at 16:40
  • This sounds strange and wrong. If you have a collection of data that needs to move based on user input, then you should be using controls that allow this easily, i.e. Listbox, Datagrid, etc... – Brent Stewart Feb 26 '13 at 16:44
  • I've edited the question to add some context @BrentStewart – Dot NET Feb 26 '13 at 16:49
  • You can use a ListBox or Datagrid with no divider lines and still maintain your alignment using grids using either fixed widths or SharedSizeGroup. I do this exact thing in one of the apps I am currently working on. – Brent Stewart Feb 26 '13 at 16:55
  • `The reason for my choice of using Grid is because I want the following dynamically generated components to be aligned neatly.` - That's what an `ItemsControl` is for. I guess you're doing it the winforms way and creating UI elements in code. That's a wrong approach in WPF. Create a proper ViewModel to hold your data and use an `ItemsControl` with a proper `DataTemplate` to display it. – Federico Berasategui Feb 26 '13 at 16:57
  • Post the code you're using to create the UI elements. – Federico Berasategui Feb 26 '13 at 16:59
  • @HighCore - Yes, they are generated the Winforms way. I'm aware that it's wrong, however I have strict requirements and an approaching deadline, so I have to work with what is given to me. – Dot NET Feb 26 '13 at 17:29
  • @HighCore - Code added :) – Dot NET Feb 26 '13 at 17:31

3 Answers3

3

Here is a quick example of using an ItemsControl to do what you are wanting:

ViewModel

public class ListBoxViewModel
{
    private static readonly  List<string> sortList = new List<string>() { "Unsorted", "Sorted" };
    public List<string> SortList { get { return sortList; } }

    public ObservableCollection<ItemDetail> ItemDetails { get; set; }

    #region Up Command
    ICommand upCommand;
    public ICommand UpCommand
    {
        get
        {
            if (upCommand == null)
            {
                upCommand = new RelayCommand(UpExecute);
            }
            return upCommand;
        }
    }

    private void UpExecute(object param)
    {
        var id = param as ItemDetail;

        if (id != null)
        {
            var curIndex = ItemDetails.IndexOf(id);
            if (curIndex > 0)
                ItemDetails.Move(curIndex, curIndex - 1);
        }
    }
    #endregion Up Command

    #region Down Command
    ICommand downCommand;
    public ICommand DownCommand
    {
        get
        {
            if (downCommand == null)
            {
                downCommand = new RelayCommand(DownExecute);
            }
            return downCommand;
        }
    }

    private void DownExecute(object param)
    {
        var id = param as ItemDetail;
        if (id != null)
        {
            var curIndex = ItemDetails.IndexOf(id);
            if (curIndex < ItemDetails.Count-1)
                ItemDetails.Move(curIndex, curIndex + 1);
        }
    }
    #endregion Down Command

    public ListBoxViewModel()
    {
        ItemDetails = new ObservableCollection<ItemDetail>()
        {
            new ItemDetail() { IsSelected = false, ItemName = "Customer Id", SortOrder = "Unsorted" },
            new ItemDetail() { IsSelected = true, ItemName = "Customer Name", SortOrder = "Sorted" },
            new ItemDetail() { IsSelected = false, ItemName = "Customer Age", SortOrder = "Unsorted" }
        };
    }
}

ItemDetail Class (Made up by me to make things easier)

public class ItemDetail
{
    public bool IsSelected { get; set; }
    public string ItemName { get; set; }
    public string SortOrder { get; set; }
}

XAML

<UserControl.Resources>     
    <DataTemplate DataType="{x:Type vm:ItemDetail}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition SharedSizeGroup="CheckBoxGroup" />
                <ColumnDefinition SharedSizeGroup="ItemNameGroup" />
                <ColumnDefinition SharedSizeGroup="SortGroup" />
                <ColumnDefinition Width="20" />
                <ColumnDefinition SharedSizeGroup="UpArrowGroup" />
                <ColumnDefinition SharedSizeGroup="DownArrowGroup" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <CheckBox Grid.Column="0" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" />
            <Label Grid.Column="1" Content="{Binding ItemName}" />
            <ComboBox Grid.Column="2" ItemsSource="{Binding DataContext.SortList, RelativeSource={RelativeSource AncestorType={x:Type views:ListBoxExample}}}" SelectedItem="{Binding SortOrder}" />
            <Button Grid.Column="4" Command="{Binding DataContext.UpCommand, RelativeSource={RelativeSource AncestorType={x:Type views:ListBoxExample}}}" CommandParameter="{Binding}">
                <Image Source="..\images\up.png" Height="10" />
            </Button>
            <Button Grid.Column="5" Command="{Binding DataContext.DownCommand, RelativeSource={RelativeSource AncestorType={x:Type views:ListBoxExample}}}" CommandParameter="{Binding}">
                <Image Source="..\images\down.png" Height="10" />
            </Button>
        </Grid>
    </DataTemplate>
</UserControl.Resources>

<Grid Grid.IsSharedSizeScope="True">
    <ItemsControl ItemsSource="{Binding ItemDetails}" />
</Grid>

And finally the results:

enter image description here

And after pressing the down arrow on the first item:

enter image description here

Hope this helps.

Brent Stewart
  • 1,830
  • 14
  • 21
  • This is how I would do it. – mdm20 Feb 26 '13 at 17:58
  • Thanks for the detailed answer. May I ask what RelayCommand does? – Dot NET Feb 26 '13 at 18:37
  • It is Josh Smith's ICommand implementation. You can find a great article on WPF from Josh on MSDN that details the RelayCommand here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx – Brent Stewart Feb 26 '13 at 18:47
  • Thanks for the reply. Unfortuantely I'm having trouble compiling the code. Could you show me your "vm" namespace path? It's telling me that the VM class cannot be found – Dot NET Feb 26 '13 at 18:56
  • It is just a namespace where my viewmodel class is located. In my case it is: xmlns:vm="clr-namespace:Wpf_Playground.ViewModels", but it will be different for you. I also declare a "views" namespace that points to the namespace where my view lives: xmlns:views="clr-namespace:Wpf_Playground.Views". – Brent Stewart Feb 26 '13 at 19:04
3

This would be the WPF approach to what you're screenshot looks like:

<Window x:Class="WpfApplication4.Window9"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window9" Height="300" Width="500">
    <ItemsControl ItemsSource="{Binding Columns}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <DataTemplate.Resources>
                    <BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
                </DataTemplate.Resources>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="25"/>
                        <ColumnDefinition Width="25"/>
                    </Grid.ColumnDefinitions>

                    <!-- This is your Key image, I used a rectangle instead, you can change it -->
                    <Rectangle Fill="Yellow" Visibility="{Binding IsPrimaryKey, Converter={StaticResource BoolToVisConverter}}"  Margin="2"/>

                    <CheckBox IsChecked="{Binding IsSelected}" Grid.Column="1"/>

                    <TextBlock Text="{Binding Name}" Grid.Column="2"/>

                    <ComboBox ItemsSource="{Binding SortOrders}" SelectedItem="{Binding SortOrder}" Grid.Column="3" Margin="2"/>

                    <Button Content="Up" Grid.Column="4" Margin="2"
                            Command="{Binding DataContext.MoveUpCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}"
                            CommandParameter="{Binding}"/>

                    <Button Content="Down" Grid.Column="5" Margin="2"
                            Command="{Binding DataContext.MoveDownCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}"
                            CommandParameter="{Binding}"/>

                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

Code Behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using InduraClientCommon.MVVM;
using System.Collections.ObjectModel;

namespace WpfApplication4
{
    public partial class Window9 : Window
    {
        public Window9()
        {
            InitializeComponent();

            var vm = new ColumnListViewModel();
            vm.Columns.Add(new ColumnViewModel() { IsPrimaryKey = true, Name = "Customer ID", SortOrder = SortOrder.Ascending });
            vm.Columns.Add(new ColumnViewModel() {Name = "Customer Name", SortOrder = SortOrder.Descending});
            vm.Columns.Add(new ColumnViewModel() {Name = "Customer Age", SortOrder = SortOrder.Unsorted});

            DataContext = vm;
        }
    }
}

ViewModel:

 public class ColumnListViewModel: ViewModelBase
    {
        private ObservableCollection<ColumnViewModel> _columns;
        public ObservableCollection<ColumnViewModel> Columns
        {
            get { return _columns ?? (_columns = new ObservableCollection<ColumnViewModel>()); }
        }

        private DelegateCommand<ColumnViewModel> _moveUpCommand;
        public DelegateCommand<ColumnViewModel> MoveUpCommand
        {
            get { return _moveUpCommand ?? (_moveUpCommand = new DelegateCommand<ColumnViewModel>(MoveUp, x => Columns.IndexOf(x) > 0)); }
        }

        private DelegateCommand<ColumnViewModel> _moveDownCommand;
        public DelegateCommand<ColumnViewModel> MoveDownCommand
        {
            get { return _moveDownCommand ?? (_moveDownCommand = new DelegateCommand<ColumnViewModel>(MoveDown, x => Columns.IndexOf(x) < Columns.Count)); }
        }

        private void MoveUp(ColumnViewModel item)
        {
            var index = Columns.IndexOf(item);
            Columns.Move(index, index - 1);
            MoveUpCommand.RaiseCanExecuteChanged();
            MoveDownCommand.RaiseCanExecuteChanged();
        }

        private void MoveDown(ColumnViewModel item)
        {
            var index = Columns.IndexOf(item);
            Columns.Move(index, index + 1);
            MoveUpCommand.RaiseCanExecuteChanged();
            MoveDownCommand.RaiseCanExecuteChanged();
        }
    }

    public class ColumnViewModel: ViewModelBase
    {
        private bool _isPrimaryKey;
        public bool IsPrimaryKey
        {
            get { return _isPrimaryKey; }
            set
            {
                _isPrimaryKey = value;
                NotifyPropertyChange(() => IsPrimaryKey);
            }
        }

        private bool _isSelected;
        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                _isSelected = value;
                NotifyPropertyChange(() => IsSelected);
            }
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                NotifyPropertyChange(() => Name);
            }
        }

        private List<SortOrder> _sortOrders;
        public List<SortOrder> SortOrders
        {
            get { return _sortOrders ?? (_sortOrders = Enum.GetValues(typeof(SortOrder)).OfType<SortOrder>().ToList()); }
        }

        private SortOrder _sortOrder;
        public SortOrder SortOrder
        {
            get { return _sortOrder; }
            set
            {
                _sortOrder = value;
                NotifyPropertyChange(() => SortOrder);
            }
        }
    }

    public enum SortOrder {Unsorted, Ascending, Descending}
}

This is what it looks like in my screen:

enter image description here

As you can see in the above example, I am in no way manipulating or creating UI elements in code, because it's actually not necessary. Whenever you need to interact with the pieces of information displayed in the screen, you interact with the ViewModels and not the View. This is the clear separation of concerns between UI and application logic WPF makes possible, which is utterly absent in other frameworks. Please consider this approach the de-facto default when doing any kind o N-element UIs in WPF.

Edit:

Advantages of this approach versus the classic one:

  • No need to manipulate complex WPF classes (I.E UI elements) in your code in order to show / get data from screen (just simple, simple properties and INotifyPropertyChanged)
  • Scales better (UI can be anything as long as it honors the ViewModel properties, you could change the ComboBox to a rotating 3d pink elephant with a Sort order in each foot.
  • No need to navigate the visual tree to find elements located God knows where.
  • No need to foreach anything. Just a simple Select that converts your data (from whatever data source you obtained it) to the ViewModel list.

Bottom line: WPF is much simpler and nicer than anything else currently in existence, if you use the WPF approach.

Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • @DotNet Im using [Expression-based NotifyPropertyChange](http://stackoverflow.com/questions/2711435/typesafe-notifypropertychanged-using-linq-expressions) in the ViewModelBase class. – Federico Berasategui Feb 26 '13 at 19:22
  • @DotNET as an aside comment, it's always better to do the things the right way, because in the end that is what helps you accomplish the dead lines and whatnot. Take a second to analyze how would you have done to re-read all data from the UI elements you created in code, say, once the user presses `Save` or something. – Federico Berasategui Feb 26 '13 at 19:30
  • I agree with you completely HighCore :) The only thing is that if I had to change this part of the application, most of the very heavy code-behind would have to go. I have to weigh the pros and cons I guess – Dot NET Feb 26 '13 at 19:45
1

You are changing the order of the RowDefinitions, which is not what you want. You want to change the assignment of elements to rows, which is determined by the Grid.Row attached property

I would put all controls that belong to each row in a container (one per row) and then use Grid.SetRow to change the containers around. See how to change the grid row of the control from code behind in wpf.

Community
  • 1
  • 1
sinelaw
  • 16,205
  • 3
  • 49
  • 80
  • The problem with that is I want the rows to be aligned perfectly though – Dot NET Feb 26 '13 at 16:50
  • Then you should either iterate over the controls in the grid and call Grid.SetRow on them, or use something else (like ListBox or DataGrid as suggested by Brent Stewart's comment) – sinelaw Feb 26 '13 at 16:57