I am trying to write a Dual List usercontrol in wpf.
I am new to wpf and I am finding it quite difficult.
This is something I have put together in a couple of hours.It’s not that good but a start.
I would be extremely grateful if somebody with wpf experience could improve it.
The aim is to simplify the usage as much as possible
I am kind of stuck.
I would like the user of the DualList Control to be able to set up headers how do you do that.
Do I need to expose some dependency properties in my control?
At the moment when loading the user has to pass a ObservableCollection is there a better way?
Could you have a look and possibly make any suggestions with some code?
Thanks a lot!!!!!
xaml
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="25px"></ColumnDefinition>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" Grid.Column="0" Grid.Row="0">
<Label Name="lblLeftTitle" Content="Available"></Label>
<ListView Name="lvwLeft">
</ListView>
</StackPanel>
<WrapPanel Grid.Column="1" Grid.Row="0">
<Button Name="btnMoveRight" Content=">" Width="25" Margin="0,35,0,0" Click="btnMoveRight_Click" />
<Button Name="btnMoveAllRight" Content=">>" Width="25" Margin="0,05,0,0" Click="btnMoveAllRight_Click" />
<Button Name="btnMoveLeft" Content="<" Width="25" Margin="0,25,0,0" Click="btnMoveLeft_Click" />
<Button Name="btnMoveAllLeft" Content="<<" Width="25" Margin="0,05,0,0" Click="btnMoveAllLeft_Click" />
</WrapPanel>
<StackPanel Orientation="Vertical" Grid.Column="2" Grid.Row="0">
<Label Name="lblRightTitle" Content="Selected"></Label>
<ListView Name="lvwRight">
</ListView>
</StackPanel>
</Grid>
Client
public partial class DualListTest
{
public ObservableCollection<ListViewItem> LeftList { get; set; }
public ObservableCollection<ListViewItem> RightList { get; set; }
public DualListTest()
{
InitializeComponent();
LoadCustomers();
LoadDualList();
}
private void LoadDualList()
{
dualList1.Load(LeftList, RightList);
}
private void LoadCustomers()
{
//Pretend we are getting a list of Customers from a repository.
//Some go in the left List(Good Customers) some go in the Right List(Bad Customers).
LeftList = new ObservableCollection<ListViewItem>();
RightList = new ObservableCollection<ListViewItem>();
var customers = GetCustomers();
foreach (var customer in customers)
{
if (customer.Status == CustomerStatus.Good)
{
LeftList.Add(new ListViewItem { Content = customer });
}
else
{
RightList.Add(new ListViewItem{Content=customer });
}
}
}
private static IEnumerable<Customer> GetCustomers()
{
return new List<Customer>
{
new Customer {Name = "Jo Blogg", Status = CustomerStatus.Good},
new Customer {Name = "Rob Smith", Status = CustomerStatus.Good},
new Customer {Name = "Michel Platini", Status = CustomerStatus.Good},
new Customer {Name = "Roberto Baggio", Status = CustomerStatus.Good},
new Customer {Name = "Gio Surname", Status = CustomerStatus.Bad},
new Customer {Name = "Diego Maradona", Status = CustomerStatus.Bad}
};
}
}
UserControl
public partial class DualList:UserControl
{
public ObservableCollection<ListViewItem> LeftListCollection { get; set; }
public ObservableCollection<ListViewItem> RightListCollection { get; set; }
public DualList()
{
InitializeComponent();
}
public void Load(ObservableCollection<ListViewItem> leftListCollection, ObservableCollection<ListViewItem> rightListCollection)
{
LeftListCollection = leftListCollection;
RightListCollection = rightListCollection;
lvwLeft.ItemsSource = leftListCollection;
lvwRight.ItemsSource = rightListCollection;
EnableButtons();
}
public static DependencyProperty LeftTitleProperty = DependencyProperty.Register("LeftTitle",
typeof(string),
typeof(Label));
public static DependencyProperty RightTitleProperty = DependencyProperty.Register("RightTitle",
typeof(string),
typeof(Label));
public static DependencyProperty LeftListProperty = DependencyProperty.Register("LeftList",
typeof(ListView),
typeof(DualList));
public static DependencyProperty RightListProperty = DependencyProperty.Register("RightList",
typeof(ListView),
typeof(DualList));
public string LeftTitle
{
get { return (string)lblLeftTitle.Content; }
set { lblLeftTitle.Content = value; }
}
public string RightTitle
{
get { return (string)lblRightTitle.Content; }
set { lblRightTitle.Content = value; }
}
public ListView LeftList
{
get { return lvwLeft; }
set { lvwLeft = value; }
}
public ListView RightList
{
get { return lvwRight; }
set { lvwRight = value; }
}
private void EnableButtons()
{
if (lvwLeft.Items.Count > 0)
{
btnMoveRight.IsEnabled = true;
btnMoveAllRight.IsEnabled = true;
}
else
{
btnMoveRight.IsEnabled = false;
btnMoveAllRight.IsEnabled = false;
}
if (lvwRight.Items.Count > 0)
{
btnMoveLeft.IsEnabled = true;
btnMoveAllLeft.IsEnabled = true;
}
else
{
btnMoveLeft.IsEnabled = false;
btnMoveAllLeft.IsEnabled = false;
}
if (lvwLeft.Items.Count != 0 || lvwRight.Items.Count != 0) return;
btnMoveLeft.IsEnabled = false;
btnMoveAllLeft.IsEnabled = false;
btnMoveRight.IsEnabled = false;
btnMoveAllRight.IsEnabled = false;
}
private void MoveRight()
{
while (lvwLeft.SelectedItems.Count > 0)
{
var selectedItem = (ListViewItem)lvwLeft.SelectedItem;
LeftListCollection.Remove(selectedItem);
RightListCollection.Add(selectedItem);
}
lvwRight.ItemsSource = RightListCollection;
lvwLeft.ItemsSource = LeftListCollection;
EnableButtons();
}
private void MoveAllRight()
{
while (lvwLeft.Items.Count > 0)
{
var item = (ListViewItem)lvwLeft.Items[lvwLeft.Items.Count - 1];
LeftListCollection.Remove(item);
RightListCollection.Add(item);
}
lvwRight.ItemsSource = RightListCollection;
lvwLeft.ItemsSource = LeftListCollection;
EnableButtons();
}
private void MoveAllLeft()
{
while (lvwRight.Items.Count > 0)
{
var item = (ListViewItem)lvwRight.Items[lvwRight.Items.Count - 1];
RightListCollection.Remove(item);
LeftListCollection.Add(item);
}
lvwRight.ItemsSource = RightListCollection;
lvwLeft.ItemsSource = LeftListCollection;
EnableButtons();
}
private void MoveLeft()
{
while (lvwRight.SelectedItems.Count > 0)
{
var selectedCustomer = (ListViewItem)lvwRight.SelectedItem;
LeftListCollection.Add(selectedCustomer);
RightListCollection.Remove(selectedCustomer);
}
lvwRight.ItemsSource = RightListCollection;
lvwLeft.ItemsSource = LeftListCollection;
EnableButtons();
}
private void btnMoveLeft_Click(object sender, RoutedEventArgs e)
{
MoveLeft();
}
private void btnMoveAllLeft_Click(object sender, RoutedEventArgs e)
{
MoveAllLeft();
}
private void btnMoveRight_Click(object sender, RoutedEventArgs e)
{
MoveRight();
}
private void btnMoveAllRight_Click(object sender, RoutedEventArgs e)
{
MoveAllRight();
}
}
After looking over all of your code, it is clear to me that you are doing this the old WinForms way, rather than the WPF way. This isn’t necessarily bad– it still works, but it will be quite difficult to maintain. Rather than taking advantage of the many tools WPF gives us, such as data-binding, commands, templates, and dependency properties, you are pretty much wiring everything up with event handlers and writing a lot of code to maintain the UI. Using some of your original XAML and code, I have constructed a example that uses all of the aforementioned features. Rather than separate it into a
UserControl, I have simply written it all in oneWindowfor ease of demonstration. First, the XAML:Starting from the top, the most important thing you will notice is that I am declaring an
ObjectDataProviderand twoCollectionViewSourceobjects. The data provider is bound to a method that will create your default customer list. The view sources take that list (of all customers) and filters them into two separate lists- one for good customers, and the other for bad customers. This is done through theCollectionViewSource.Filterproperty.Next, you will see the user interface as you originally constructed it, but rather than wire up event handlers, I have bound the buttons to commands on the window. The
ListView.ItemSourceproperty is bound in the XAML, to theGoodCustomersandBadCustomerssources, respectively. All of these bindings will remove a good chunk of your boilerplate user-interface code. Now let’s take a look at the code-behind:Starting from the top, you will see an
ICommanddeclaration for each button. I have also added two dependency properties that represent the right and left headers for your lists (changing these properties will automatically update the headers in the UI). Then, in the constructor, I hook up each command to aRelayCommand(created by Josh Smith), which simply lets me specify two delegates- one for when the command is executed, another that controls when the command can be executed. You can see that rather than moving items in between lists, I am simply changing the property on the item by which the list is filtered. So to move an item left, I change the customer status to good. This is an example of separation of UI and business logic: the UI reflects changes made to the underlying items, but does not make the changes itself.The logic for each command should be fairly easy to understand- to move all customers to the good list, we simply iterate over each customer, setting their
Statusto good. Rinse and repeat for the other commands. Note that we must update ourCollectionViewSourceobjects to make the filter update. Otherwise no changes would be shown.So, in summary:
CollectionViewSourceandObjectDataProvidercan be used to filter and display your data the way you want it to. In your scenario, rather than manage two lists, I have a single list that gets filtered based on the customer status.