My first StackExchange question, so tell me if I do anything wrong.
I'm converting old winforms code to WPF, and am leaning them both along the way. I've been successful most of the time, but I'm tacking my first Data Table involving Data Binding. It seems to be the hardest thing to convert since binding is handled mostly in xaml rather than code.
As an additional note, it uses CSLA, which to my understanding is a code structure similar to MVVM(still learning what these are).
With a bit of experimenting, I managed to create a functioning data grid, but I need to implement one of the columns as a combo box. It is represented currently as a number(1-10), but I need the description that goes with the number, which is in a different table in the database.
The number that was originally displayed before switching it to a combobox was noneligibilityreason. I want this value to be the currently selected item in the combobox.
Can't post images, so here's my attempt at showing the structure of the database tables:
modreview
noneligreason(this is the subtable that aligns with noneligibilityreason)
The code included this function, which seems to create a collection that should be, I think, the ItemsSource:
NERList.GetNameValueList();
As for the ItemsSource, I want all of the noneligreasondesc values. Of course then there is the matter of getting the noneligibilityreason to match up to the noneligreasonid. I found this example, which seems to do the same thing, but without the database, using classes instead:
I tried to duplicate this as I thought it should work here in my XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTestProject" x:Class="WpfTestProject.MainWindow"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<local:caseviewDataSet x:Key="caseviewDataSet"/>
<CollectionViewSource x:Key="modreviewViewSource"
Source="{Binding modreview, Source={StaticResource caseviewDataSet}}"/>
<CollectionViewSource x:Key="noneligibilityViewSource"
Source="{Binding noneligreason, Source={StaticResource caseviewDataSet}}"/>
</Window.Resources>
<Grid DataContext="{StaticResource modreviewViewSource}">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<DataGrid x:Name="modreviewDataGrid"
RowDetailsVisibilityMode="VisibleWhenSelected"
ItemsSource="{Binding}"
EnableRowVirtualization="True"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="reviewnumColumn" Width="SizeToHeader" Header="reviewnum" Binding="{Binding reviewnum}"/>
<DataGridTextColumn x:Name="intcasenoColumn" Width="SizeToHeader" Header="intcaseno" Binding="{Binding intcaseno}"/>
<DataGridCheckBoxColumn x:Name="newreferralColumn" Width="SizeToHeader" Header="newreferral" Binding="{Binding newreferral}"/>
<DataGridTemplateColumn x:Name="screendateColumn" Width="Auto" Header="screendate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding screendate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridCheckBoxColumn x:Name="eligibleColumn" Width="SizeToHeader" Header="eligible" Binding="{Binding eligible}"/>
<DataGridComboBoxColumn x:Name="noneligibilityreasonColumn" Width="SizeToHeader" Header="noneligibilityreason"
ItemsSource="{Binding Source={StaticResource noneligibilityViewSource}}"
SelectedValueBinding="{Binding noneligibilityreason}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
/>
</DataGrid.Columns>
</DataGrid>
</Grid>
And the code behind:
using CslaFactoryBusinessObjects;
...
namespace WpfTestProject
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
WpfTestProject.caseviewDataSet caseviewDataSet = ((WpfTestProject.caseviewDataSet)(this.FindResource("caseviewDataSet")));
// Load data into the table modreview. You can modify this code as needed.
WpfTestProject.caseviewDataSetTableAdapters.modreviewTableAdapter caseviewDataSetmodreviewTableAdapter = new WpfTestProject.caseviewDataSetTableAdapters.modreviewTableAdapter();
caseviewDataSetmodreviewTableAdapter.Fill(caseviewDataSet.modreview);
System.Windows.Data.CollectionViewSource modreviewViewSource = ((CollectionViewSource)(this.FindResource("modreviewViewSource")));
modreviewViewSource.View.MoveCurrentToFirst();
noneligibilityreasonColumn.ItemsSource = NERList.GetNameValueList();
}
}
}
Most of this code was generated when I drag dropped from Data Sources, but the last line in the code behind is where I think I was supposed to add the ItemsSource. Not sure if it belongs in Loaded, but it seems like it might be OK there. Also not sure if there is a way to do this in XAML instead.
I realize I duplicated setting ItemsSource in XAML and code, but neither one works properly, so I included both to show what options I have tried.
Finally, I think I should show the old winforms code that I'm trying to emulate in the WPF conversion(Not sure if this is enough of the code). I think it uses a hidden combobox to set up the binding, then adds it to the table:
//from Program.cs used in setupModRvwGrdHdr()
public static void ListControlBinding(ref UltraCombo comboBox, object lkupdataSource, string displayMember,
string valueMember, object objDataSource, string objProp) {
comboBox.DataSource = lkupdataSource;
comboBox.DisplayMember = displayMember;
if (!string.IsNullOrEmpty(valueMember))
comboBox.ValueMember = valueMember;
if (objDataSource != null)
comboBox.DataBindings.Add("Value", objDataSource, objProp);
}
//from the code for the specific winform
private void setupModRvwGrdHdr() {
cbNonEligReason.DataBindings.Clear();
grdModReviews.DataSource = bsModRvws;
Program.ListControlBinding(ref cbNonEligReason, NERList.GetNameValueList(), "Value", "Key", bsModRvws,
"NonEligibleReasonIDStr");
cbNonEligReason.DisplayLayout.Bands[0].Columns["Key"].Hidden = true;
cbNonEligReason.DisplayLayout.Bands[0].Columns["Value"].Header.Caption = "Noneligiblity Reason";
grdModReviews.DisplayLayout.Bands[0].Columns["reviewnum"].CellActivation = Activation.NoEdit;
grdModReviews.DisplayLayout.Bands[0].Columns["intcaseno"].Hidden = true;
grdModReviews.DisplayLayout.Bands[0].Columns["noneligibilityreason"].Hidden = true;
grdModReviews.DisplayLayout.Bands[0].Columns["screendate"].Hidden = false;
grdModReviews.DisplayLayout.Bands[0].Columns["screendate"].Header.Caption = "Date of Screen";
grdModReviews.DisplayLayout.Bands[0].Columns["screendate"].Width = 100;
grdModReviews.DisplayLayout.Bands[0].Columns["screendate"].EditorComponent = dteModRvwDate;
grdModReviews.DisplayLayout.Bands[0].Columns["eligible"].Hidden = false;
grdModReviews.DisplayLayout.Bands[0].Columns["eligible"].Header.Caption = "Eligible";
grdModReviews.DisplayLayout.Bands[0].Columns["eligible"].Width = 70;
grdModReviews.DisplayLayout.Bands[0].Columns["NonEligibleReasonIDStr"].Hidden = false;
grdModReviews.DisplayLayout.Bands[0].Columns["NonEligibleReasonIDStr"].Header.Caption =
"Reason for Noneligibility";
grdModReviews.DisplayLayout.Bands[0].Columns["NonEligibleReasonIDStr"].Width = 250;
grdModReviews.DisplayLayout.Bands[0].Columns["NonEligibleReasonIDStr"].EditorComponent = cbNonEligReason;
grdModReviews.DisplayLayout.Bands[0].Columns["NonEligibleReasonIDStr"].Nullable =
Infragistics.Win.UltraWinGrid.Nullable.Nothing;
}
This is a fairly large program, and there might be some helpful functions in the CslaFactoryBusinessObjects classes, but I think I should learn how to do this to better understand data manipulation in WPF.
I've been looking for a solution for days now and haven't found a case similar enough to mine. I'm just not sure exactly how each of the binding properties works and how they apply in this specific case. Finally had to throw in the towel and make an account here. Sorry for the length, but I wanted to be specific and show that I have been at this for a while.
Please Help!
There is a lot of helpful information here, and a good start for a SO question.
Given your XAML binding:
<DataGridComboBoxColumn x:Name="noneligibilityreasonColumn" Width="SizeToHeader" Header="noneligibilityreason"
ItemsSource="{Binding Source={StaticResource noneligibilityViewSource}}"
SelectedValueBinding="{Binding noneligibilityreason}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
/>
What this is saying is that (for each row from the DataGrid - which in turn is a modreview
object/ row out of the DataTable inside the DataSet) the combobox control should use the noneligibilityViewSource
for its list of selectable items.
(Let's ignore the contradictory setting of item source in the Window_Loaded
event for now).
It also says that for the combobox's Display (that which is shown visually in the control) should come from the "Value" property as specified in the DisplayMemberPath
. This will correspond to the same named property from item in the collection specified by the ItemSource
.
Since the combobox's items are provided by the ItemSource binding, that then will come from the noneligibilityViewSource
, so the next question is what is inside this noneligibilityViewSource
?
You have it declared as a resource for this Window:
<CollectionViewSource x:Key="noneligibilityViewSource" Source="{Binding noneligreason, Source={StaticResource caseviewDataSet}}" />
The above states that the CollectionViewSource instance (aka noneligibilityViewSource
) is coming from a property called noneligreason
on the caseviewDataSet
. Given the way DataSets work, I'm expecting either a DataTable by the name of noneligreason
or a customised DataSet with an added property for that. Likely the former.
Now, you have contradictory code in the Window_Loaded
event that programmatically sets the ItemSource for the combobox to something else. Specifically to the result of the call to NERList.GetNameValueList();
. I say contradictory because the XAML resource declaration says this list of noneligibility values are coming from the correspondingly named property on the DataSet, whilst the code in the event says to use a CSLA Business Object list.
You'll have to figure out which one you want to/should use.
PS: Likely, if the noneligibility property in the DataSet contains the data, then there is no need to pay the performance hit of accessing the database again with the call to NERList.GetNameValueList();
because you have the data available already.
Once you've determined which "source" contains the list of items for your combobox - ie. the noneligreason
DataTable in the caseviewDataSet
or from the CSLA Business Object list returned by the call to NERList.GetNameValueList();
- only then can you know which property should be used for the DisplayMemberPath and which property for the SelectedValuePath.
So if the ItemSource for the combobox is as per the declared XAML and the caseviewDataSet has another DataTable called noneligreason
, then from that you need to find out what the names of the properties are. For example, it could be noneligreasonid
and noneligreasondesc
, but it could be something else depending on what the table adaptor does. Your binding's DisplayMemberPath
could then be noneligreasondesc
and your SelectedValuePath
could then be noneligreasonid
.
If the ItemSource for the combobox should come from the call to NERList.GetNameValueList();
then again, you need to determine what the property names of the objects returned are. From the naming convention I'm guessing that it's "Name" and "Value" which means the DisplayMemberPath
should be Value
and the SelectedValuePath
should be set to Name
. Guessing is no good, so go and look at that object, or use the debugger to inspect the values.
The SelectedValueBinding
property refers to the property on the modreview
row object that should contain the value of the selected combobox item, specifically the SelectedValuePath
property from the combobox item will be applied to the SelectedValueBinding
property on the modreview
instance.
The following MSDN documentation should help you understand what the various properties on the DataGridComboBoxColumn are for. https://msdn.microsoft.com/en-us/library/system.windows.controls.datagridcomboboxcolumn(v=vs.110).aspx
So for example your XAML could change to be either one of the following two declarations:
<DataGridComboBoxColumn x:Name="noneligibilityreasonColumn" Width="SizeToHeader" Header="noneligibilityreason"
ItemsSource="{Binding Source={StaticResource noneligibilityViewSource}}"
SelectedValueBinding="{Binding noneligibilityreason}"
DisplayMemberPath="noneligreasondesc"
SelectedValuePath="noneligreasonid"
/>
or
<DataGridComboBoxColumn x:Name="noneligibilityreasonColumn" Width="SizeToHeader" Header="noneligibilityreason"
ItemsSource="{Binding Source={StaticResource noneligibilityViewSource}}"
SelectedValueBinding="{Binding noneligibilityreason}"
DisplayMemberPath="Value"
SelectedValuePath="Name"
/>
Hope that helps.