Silverlight dataform MVVM command binding on update - silverlight

I'm new to MVVM and Silverlight and I'm just trying to figure out a simple scenario.
I'm using the MVVM Light toolkit and Silverlight 3.0 without Expression Blend.
I have a DataGrid and a DataForm bound to an observable collection in a ViewModel. I would like to bind to my RelayCommand Save() property after I make changes to the data in the DataForm control and have this accomplished without using the code behind for my view.
The DataForm doesn't use the cmd:ButtonBaseExtensions.Command that MVVM Light uses for normal button click command binding, so I'm not sure how to tie the control to my ViewModel.
Any help is appreciated!

I figured it out shortly after posting the question. Go figure.
When using the MVVM Light Toolkit you can bind to events using the EventToCommand feature.
My Xaml looks like this:
<UserControl x:Class="CountyBusinessDirectory.UI.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:dataFormToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight"
xmlns:cmdextras="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
DataContext="{Binding BusinessesViewModel, Source={StaticResource Locator}}">
<Grid x:Name="LayoutRoot" ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<data:DataGrid x:Name="dgAllBusinesses" CanUserSortColumns="True"
IsReadOnly="True" AutoGenerateColumns="True"
ItemsSource="{Binding Businesses}"
Grid.Column="0">
</data:DataGrid>
<ScrollViewer x:Name="svScroll" Grid.Column="1" >
<dataFormToolkit:DataForm x:Name="dfDetails"
ItemsSource="{Binding Businesses}"
AutoGenerateFields="True"
CommitButtonContent="Save"
CommandButtonsVisibility="Edit, Navigation, Commit, Cancel" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="EditEnded">
<cmdextras:EventToCommand Command="{Binding SaveBusiness}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</dataFormToolkit:DataForm>
</ScrollViewer>
</Grid>
And my ViewModel looks like this (using direct silverlight enabled WCF service in ViewModel for quick example, would normally pull this into an interface to decouple):
//using statements ommitted for brevity
namespace MyProject.ViewModels
{
public class BusinessesViewModel : ViewModelBase
{
private PagedCollectionView _businesses;
DALServiceClient _proxy;
public RelayCommand SaveBusiness
{ get; private set; }
public PagedCollectionView Businesses
{
get
{
return _businesses;
}
set
{
if (_businesses != value)
{
_businesses = value;
base.RaisePropertyChanged("Businesses");
}
}
}
public BusinessesViewModel()
{
_proxy = new DALServiceClient(); //Data Access Layer WCF Service
_proxy.GetBusinessesCompleted += new EventHandler<GetBusinessesCompletedEventArgs>(_proxy_GetBusinessesCompleted);
_proxy.GetBusinessesAsync();
SaveBusiness = new RelayCommand(() => SaveBusinessToDB());
}
private void SaveBusinessToDB()
{
Business bus = Businesses.CurrentItem as Business;
_proxy.UpdateBusinessesAsync(bus);
}
void _proxy_GetBusinessesCompleted(object sender, GetBusinessesCompletedEventArgs e)
{
if (e.Result != null)
{
Businesses = new PagedCollectionView(e.Result);
}
}
}
}

Related

WPF access to another object

I have MainWindow with ContentControl like this:
<Window x:Class="Prog.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Prog"
mc:Ignorable="d"
Title="MainWindow" Height="700" Width="800" Background="Black">
<Grid>
<ContentControl x:Name="contentControl" />
</Grid>
</Window>
Now in constructor i assign my UserControl to "contentControl" which contains one button. What i want to achieve is after clicking on this button another UserControl is assigned to "contentControl". I've tried to create public function in MainWindow where it changes "contentControl", but I dont know how to reference MainWindow object in c#. I could only see static functions but I want to change value so i need object reference. I would appreciate any help
If using standard code behind you would access the MainWindow in your methods as such.
var mainWindow = (MainWindow)Application.Current.MainWindow;
From there you can access public properties and methods from the mainWindow variable.
Then in your UserControl you can change the ContentControl content as follows.
mainWindow.contentControl.Content=new UserControl2();
I have included a full sample showing how to access a method and a property in MainWindow from several UserControls here. https://gist.github.com/DaveCS1/1caca548c0c0caa2e34854074976e609
Hope that helps.
The usual pattern used for wpf development is mvvm.
The way I'd approach this is viewmodel first navigation.
Define a viewmodel for the window.
That would expose a property of type object ( or baseviewmodel ).
The contentcontrol would bind it's content to that property.
This would then be templated into whichever usercontrol is appropriate using the datatype mechanism to match viewmodel to view.
Changing usercontrol is then a matter of newing up a different viewmodel and setting this property to that instance.
Expose a command from the window viewmodel to do this navigation and use relativesource binding to that from your usercontrol.
This is a simple example of viewmodel first navigation:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Content="Login Page"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:LoginViewModel}"
/>
<Button Content="User Page"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:UserViewModel}"
/>
</StackPanel>
<ContentControl Grid.Column="1"
Content="{Binding CurrentViewModel}"
/>
</Grid>
The viewmodel uses mvvmlight for relaycommand:
public class MainWindowViewModel : INotifyPropertyChanged
{
private object currentViewModel;
public object CurrentViewModel
{
get { return currentViewModel; }
set { currentViewModel = value; RaisePropertyChanged(); }
}
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Your button would of course be in one of these usercontrols.
A relativesource binding looks like:
{Binding DataContext.NameOfCommandInWindowViewModel,
RelativeSource={RelativeSource AncestorType={x:Type MainWindow}}}
Let's say you really really don't want to learn MVVM for now.
You can get a reference to the window your usercontrol is in from it using:
Window.GetWindow(this);
There is a problem though.
The controls in a window are private members.
You can't just dip into some other object's private members and change stuff.
This means you'd have to add a public method to your window so you can call that.
This method in turn could take some other usercontrol as a parameter and set the content of one of it's controls as necessary.
Before you think "That sounds easier, I'll just do that" you should be aware that this is widely considered to be bad practice.

How to Add a Colmmand to a ComboBox

I am attempting to add command capability to a ComboBox. After some searching, I decided on the following approach as being the simplist:
1) Add System.Windows.Interactivity.dll to my References
2) Add the following to my XAML
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
3) Add the following to my ComboBox
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding ChangePlanner}" />
</i:EventTrigger>
</i:Interaction.Triggers>
I have two questions:
A) Is this the most straightforward approach? If not, what is?
B) If this is the right approach, why does it not work? That is, my ChangePlanner Sub is not being invoked.
Here is a quick working sample using the triggers with a ComboBox:
ViewModel
public class ShellViewModel : BindableBase
{
private string _selectedItem;
public string Title => "Sample";
public ObservableCollection<string> Items
{
get;
} = new ObservableCollection<string>(new[] { "A", "B", "C" });
public string SelectedItem
{
get => _selectedItem;
set => SetProperty(ref _selectedItem, value);
}
public ICommand ChangeCommand => new DelegateCommand<string>(s => Debug.WriteLine($"Command Executed: {s}"));
}
View
<Window x:Class="Poc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Poc.ViewModels"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="{Binding Title}" Height="350" Width="525">
<Window.DataContext>
<viewModels:ShellViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding ChangeCommand}" CommandParameter="{Binding SelectedItem}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</Grid>
Haven't seen your code posted yet, but I am going to guess that you were trying to bind to a method (and not an ICommand).
Is this the most straightforward approach? If not, what is?
The most straightforward and MVVM friendly approach would be to bind the SelectedItem of the ComboBox to a source property of your view model and handle any logic, or invoke the command, in the setter of this one:
private object _selectedItem;
public object SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
ChangePlanner.Execute(null);
}
}
why does it not work?
Impossible to say based on the information you have provided. Make sure that ChangePlanner is a public property of the DataContext of the ComboBox that returns an ICommand to begin with.

DoubleUpDown of Extended WPF Toolkit, what am I doing wrong?

Ok, I thought this would be a no-brainer, but evidently I'm doing something wrong. The problem is that when clicking on the "Up" and "Down" buttons of the Extended WPF toolkit DoubleUpDown control, the values do not get updated correctly. When I click Up, the value in the control changes, but the view model does not get updated. Only when I change from clicking Up to clicking Down, does the model get updated, but with the then previous value.
To reproduce, I used a simple view model like so:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
MyValue = 0.5;
}
private double _myValue;
public double MyValue
{
get { return _myValue; }
set
{
_myValue = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyValue"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
And my MainWindow.xaml looks like the code below, where the DoubleUpDown control and the label are both bound in TwoWay fashion to the ViewModel's MyValue property:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="100" Width="200">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<xctk:DoubleUpDown
Value="{Binding MyValue, Mode=TwoWay}"
Increment="0.5"
Minimum="0.0"
Maximum="10"
ValueChanged="DoubleUpDown_ValueChanged"
/>
<Label Grid.Column="1" Content="{Binding MyValue, Mode=TwoWay}"/>
</Grid>
</Window>
And in the code-behind, I set the DataContext in the MainWindow constructor to be an instance of ViewModel:
public MainWindow()
{
DataContext = new ViewModel();
InitializeComponent();
}
Default binding update logic for DoubleUpDown control is LostFocus. Try setting explicitly UpdateSourceTrigger=PropertyChanged in your binding like this -
<xctk:DoubleUpDown
Value="{Binding MyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Increment="0.5"
Minimum="0.0"
Maximum="10"
ValueChanged="DoubleUpDown_ValueChanged"/>

WPF-MVVM Binding ViewModel-Property to nested UserControl

As the title says, I want to bind a property from my ViewModel to a nested UserControl in the corresponding view.
I cant get it work the way I need.
The nested UserControl is nothing more than a DatePicker and a DropDown for the hours. How can I tell the DatePicker to choose the date propagated by the ViewModel as its selected date?
I tried nearly everything and now I'm not far away from jumping outside the window.
As you can see any help is appreciated ;)
Now to the code so far: DateTimePicker.xaml.cs (CodeBehind)
public partial class DateTimePicker
{
public static DependencyProperty SelectedDateValueProperty = DependencyProperty.Register("SelectedDateValue", typeof (DateTime), typeof (DateTimePicker), new FrameworkPropertyMetadata(default(DateTime), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPropertyChangedCallback));
private static void OnPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
Debug.WriteLine("Wohoo. I'm here and still debugging...");
}
public DateTimePicker()
{
InitializeComponent();
DataContext = this;
var times = GetTimes();
Times.ItemsSource = times;
Times.SelectedItem = times.First();
}
public DateTime SelectedDateValue
{
get { return (DateTime) GetValue(SelectedDateValueProperty); }
set { SetValue(SelectedDateValueProperty, value); }
}
}
The nested UserControl (DateTimePicker.xaml):
<UserControl x:Class="Framework.Controls.DateTimePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<DatePicker HorizontalAlignment="Left" Name="DatePickerCalendar" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Center" SelectedDate="{Binding SelectedDateValue}" />
<ComboBox Grid.Column="1" VerticalAlignment="Center" Name="Times" DisplayMemberPath="Name" />
</Grid>
And, last but not least: The View which has the nested UserControl (View.xaml)
<CustomControls:DateTimePicker SelectedDateValue="{Binding LocalRegistrationStartDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Hope the problem is clear and anybody can help me or get the point at what i am doing wrong here.
Using:
"{Binding SelectedDateValue}"
tells WPF "Hey check my DataContext for a property called SelectedDateValue".
What you want is, to get the Property from your user control.
The easiest way is to give your user control a name like:
<UserControl x:Name="myControl"/>
and then modify your binding to :
"{Binding ElementName=myControl, Path=SelectedDateValue}"
The usual way WPF controls are implemented is to use a template rather than defining the control as direct content, like you're doing here. By using a Template, you have access to TemplateBinding, allowing you to easily bind your control properties. See the Control Customization MSDN page.
<ControlTemplate TargetType="{x:Type local:DateTimePicker>
...
<DatePicker SelectedDate="{TemplateBinding SelectedDateValue}" />
...
</ControlTemplate>

Suggestions for synchronization of lists in Silverlight

We are creating a business application (Silverlight 4) at our company where we, in several of our views, use the functionality to synchronize two lists of some kind.
More precise we are, at the moment, using two list boxes (one source and one target) and two buttons (one add and one remove). Both the source list and the target lists are bound to collections with the same data type.
A user may select one or more items in the source list and press the add button to have the items moved to the target list (i.e. the items are removed from the source and added to the target).
Likewise the user could select one or more items in the target list and press remove to have the items moved from the target back to the source list.
There is also the possibility to add a validation rule that says that the user must add at least one item to the target list.
Pretty simple...
Until now we are using our own created user control which incapsulates these 4 controls (2 list boxes and 2 buttons) and the logic for keeping the lists in sync. Dependency properties are used for binding the source and target collections.
Now to the problem. Our customer now wants our user control to be more flexible.
They want to be able to have an arbitray number of columns in both the source and target list (i.e. the source list may have different columns and a different number of columns than the target list). The customer also wants to be able to sort on any column.
My first thought was to replace the list box to the data grid instead. But then I realized I don't know how to, in an easy way, let the consumer (the developer) define his or her columns and bindings. This may be to my limited knowledge of SL. Maybe a custom user control isn't the way to go?
I would appreciate any kind of help. Right now we are implementing the same logic over and over again in our views and it doesn't feel right. There has to be some way we can make this a reusable component that is easy to use.
Thanks!
"Grid version" of the answer:
(see list version below)
As the question has changed (clarified) I am adding a new answer. The first one is still useful for those that only want lists so I will leave it there.
To do a similar thing with grids you don't expose the templates as datagrid columns are not templated (they are lists of controls, which can individually be templated).
Instead you expose the left grid and right grid column collections as properties and simply set the LeftColumns and RightColumns properties of the control in your parent page. The only trick is that the datagrid-column collections have no setter so you need to update the grid collections items from the new property collections:
The code-behind is now:
using System.Collections.ObjectModel;
using System.Windows.Controls;
namespace SilverlightApplication1
{
public partial class GridSelectionControl : UserControl
{
public GridSelectionControl()
{
InitializeComponent();
}
public ObservableCollection<DataGridColumn> LeftColumns
{
get
{
return leftDataGrid.Columns;
}
set
{
leftDataGrid.Columns.Clear();
foreach (var col in value)
{
leftDataGrid.Columns.Add(col);
}
}
}
public ObservableCollection<DataGridColumn> RightColumns
{
get
{
return rightDataGrid.Columns;
}
set
{
rightDataGrid.Columns.Clear();
foreach (var col in value)
{
rightDataGrid.Columns.Add(col);
}
}
}
}
}
The Xaml of the control is now:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" x:Class="SilverlightApplication1.GridSelectionControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<sdk:DataGrid x:Name="leftDataGrid" Margin="10" AutoGenerateColumns="False"/>
<sdk:DataGrid x:Name="rightDataGrid" Margin="10" Grid.Column="2" AutoGenerateColumns="False"/>
<StackPanel Grid.Column="1" Orientation="Vertical">
<Button Content="Add >" Height="23" HorizontalAlignment="Left" Width="75" Margin="10" />
<Button Content="< Remove" Height="23" HorizontalAlignment="Left" Width="75" Margin="10" />
</StackPanel>
</Grid>
</UserControl>
The Xaml of the test page (which defines the columns) is now:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SilverlightApplication1" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
mc:Ignorable="d"
x:Class="SilverlightApplication1.TestGridSelectionControl"
d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<local:GridSelectionControl x:Name="SelectionControl">
<local:GridSelectionControl.LeftColumns>
<sdk:DataGridCheckBoxColumn Header="Machine?" Binding="{Binding IsMachine}"/>
<sdk:DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<sdk:DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
</local:GridSelectionControl.LeftColumns>
<local:GridSelectionControl.RightColumns>
<sdk:DataGridCheckBoxColumn Header="Machine?" Binding="{Binding IsMachine}"/>
<sdk:DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<sdk:DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
</local:GridSelectionControl.RightColumns>
</local:GridSelectionControl>
</Grid>
</UserControl>
And the test code-behind is now:
using System.Collections.ObjectModel;
using System.Windows.Controls;
namespace SilverlightApplication1
{
public partial class TestGridSelectionControl : UserControl
{
public TestGridSelectionControl()
{
// Required to initialize variables
InitializeComponent();
SelectionControl.leftDataGrid.ItemsSource = Person.People;
SelectionControl.rightDataGrid.ItemsSource = Person.Machines;
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsMachine { get; set; }
public Person(string firstname, string lastname, bool robot)
{
this.FirstName = firstname;
this.LastName = lastname;
this.IsMachine = robot;
}
public static ObservableCollection<Person> People = new ObservableCollection<Person>()
{
new Person("Tom", "Jones", false),
new Person("Elis", "Presley", false),
new Person("Joe", "Blogs", false)
};
public static ObservableCollection<Person> Machines = new ObservableCollection<Person>()
{
new Person("Marvin", "Android", true),
new Person("Hal", "9000", true),
new Person("B", "9", true)
};
}
}
}
Since you are creating a control to be consumed by other developers then its usually best to be using a Template Control rather than a UserControl. In which case the developers can specify a custom template for the control. However that's not as helpful as you could be, especially if the set of headers for both grids are the same.
One approach you could take is to provide dependency property of the type DataTemplate called "ListTemplate". At the two points in your controls Xaml where you would display the lists use two ControlPresenter elements. One named "SourceContent" the other "TargetContent". For both bind ContentTemplate to this new "ListTemplate".
Code up the assignment of the Content property on these presenters and then assign the appropriate collection the ItemsSource of either the ItemsControl or DataGrid the presenter has loaded.
If you include a simple ListBox base data template as the default value for the "ListTemplate" property then you control should be usuable in its simplest form yet if the developer wants use a DataGrid with various columns they can define one in the
ListTemplate property.
Of course you will need to write code in your control to cope with the lists possibly being a DataGrid elements.
"List version" of the answer
(see also the grid version)
A custom control sounds right for this, but you want both lists to be templated so the developer can define the per item views. I have assumed you do not need headings so have stuck with listboxes. The test code below results in this:
As the listboxes already have item templates, you really want to expose those as properties in your custom user control. You can then edit the 2 templates individually (the example below simply has both Left and Right templates set to the same FirstName/LastName stackpanel, this is where you define the format of your listboxes):
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
x:Class="SilverlightApplication1.TestListSelectionControl"
d:DesignWidth="640" d:DesignHeight="480">
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Top" Width="65" Text="{Binding FirstName}"/>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Left" VerticalAlignment="Top" Width="65" Text="{Binding LastName}"/>
</StackPanel>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<local:ListSelectionControl x:Name="SelectionControl" d:LayoutOverrides="Height" LeftItemTemplate="{StaticResource DataTemplate1}" RightItemTemplate="{StaticResource DataTemplate1}"/>
</Grid>
</UserControl>
The example ListSelectionControl XAML is below:
<UserControl x:Class="SilverlightApplication1.ListSelectionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox HorizontalAlignment="Stretch" Margin="12" Name="leftListBox" VerticalAlignment="Stretch" />
<ListBox HorizontalAlignment="Stretch" Margin="12" Name="rightListBox" VerticalAlignment="Stretch" Grid.Column="2" />
<StackPanel Grid.Column="1" Orientation="Vertical">
<Button Content="Add >" Height="23" HorizontalAlignment="Left" Width="75" Margin="10" />
<Button Content="< Remove" Height="23" HorizontalAlignment="Left" Width="75" Margin="10" />
</StackPanel>
</Grid>
</UserControl>
And the simple code-behind of the control:
using System.Windows;
using System.Windows.Controls;
namespace SilverlightApplication1
{
public partial class ListSelectionControl : UserControl
{
public DataTemplate LeftItemTemplate
{
get
{
return leftListBox.ItemTemplate;
}
set
{
leftListBox.ItemTemplate = value;
}
}
public DataTemplate RightItemTemplate
{
get
{
return rightListBox.ItemTemplate;
}
set
{
rightListBox.ItemTemplate = value;
}
}
public ListSelectionControl()
{
InitializeComponent();
}
}
}
And just to complete the example, this is the code behind to populate the sample GUI:
using System.Windows.Controls;
using System.Collections.ObjectModel;
namespace SilverlightApplication1
{
public partial class TestListSelectionControl : UserControl
{
public TestListSelectionControl()
{
// Required to initialize variables
InitializeComponent();
SelectionControl.leftListBox.ItemsSource = Person.People;
SelectionControl.rightListBox.ItemsSource = Person.Machines;
}
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstname, string lastname)
{
this.FirstName = firstname;
this.LastName = lastname;
}
public static ObservableCollection<Person> People = new ObservableCollection<Person>()
{
new Person("Tom", "Jones"),
new Person("Elis", "Presley"),
new Person("Joe", "Blogs")
};
public static ObservableCollection<Person> Machines = new ObservableCollection<Person>()
{
new Person("Marvin", "Android"),
new Person("Hal", "9000"),
new Person("B", "9")
};
}
}

Resources