WPF Databinding Link Textblock text to Combobox Selection - wpf

I have this xaml in my datagrid:
<DataGridTemplateColumn Header="Status" Width="*" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="StatusText" Text="{Binding Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox
ItemsSource="{Binding Source={StaticResource StatusItems}}"
SelectedValue="{Binding Status, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}"
DisplayMemberPath="Description"
SelectedValuePath="Status"
x:Name="Combo"
/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
When i change the value of the combobox, the dataset updates perfectly, but the textblock text doesn't update to the new value I have to refill the entire dataset for the textblock to match the newly selected combobox value. I see the correct method is to implement INotifyPropertyChanged, but that would require significant changes to the way the app fills the dataset, at least from what i understand reading similar posts. I don't have a model that i can implement on, i'm wondering if i can simply set a trigger on the textblock that will change the value whenever the combobox selection changes.
Here is how i am filling the datagrid, if someone knows how i can modify this to implement INotifyPropertyChanged, that would also be great, but i don't think that will work without a model defined (again, just going on what i see others doing).
Dim con As New SqlConnection(str)
Dim ds As New DataSet()
Dim Adpt As New SqlDataAdapter
Adpt.SelectCommand = New SqlCommand(com, con)
con.Open()
Adpt.Fill(ds, "dbo.tmfCNCComponent_threed")
dataGrid1.ItemsSource = ds.Tables("dbo.tmfCNCComponent_threed").DefaultView
con.Close()

Add this attribute to the ComboBox: IsSynchronizedWithCurrentItem="False". By default, CollectionViewSource tracks the selected item in a selector (e.g. ComboBox, ListBox, etc.). If you use the same CollectionViewSource for multiple controls, it will impose the same selection on all of them unless you explicitly prevent that. If you're using the same collection with multiple selectors, there are cases where you want them all to synchronize the selected item. This is not one of those cases.
You need a read-only CellTemplate and an editable CellEditingTemplate. We can use the same template for both, with a ComboBox that's disabled when the cell isn't being edited.
Result:
<DataGrid x:Name="DataGrid" AutoGenerateColumns="False" CellEditEnding="DataGrid_CellEditEnding">
<DataGrid.Resources>
<DataTemplate x:Key="StatusColumnTemplate">
<ComboBox
ItemsSource="{Binding Source={StaticResource StatusItems}}"
SelectedValue="{Binding Status, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Description"
SelectedValuePath="Status"
IsSynchronizedWithCurrentItem="False"
IsEnabled="{Binding IsEditing, RelativeSource={RelativeSource AncestorType=DataGridCell}}"
/>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn
Header="Status Shared"
CellTemplate="{StaticResource StatusColumnTemplate}"
CellEditingTemplate="{StaticResource StatusColumnTemplate}"
/>
What you've got now clearly can't work because the grid rows don't have a Description column.
NotifyOnSourceUpdated=True has nothing to do with anything that's happening here.

Related

Getting selected item from Datagrid Combobox Cell

I've been trying to get a selected item from a combobox in a datagrid cell. I have an datagrid which contains several comboboxes which are bound with ObservableCollection. So, the code in my WPF look like this:
<DataGrid x:Name="DgvEmployee" ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cboxName" ItemsSource="{Binding EmployeeName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Department" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding EmployeeDept}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
And in my Class:
Dim comboBoxLoad As New ObservableCollection(Of ClcomboboxBinding)
comboBoxLoad.Add(New ClcomboboxEmployee)
DataContext = comboBoxLoad
My Class ClcomboboxEmployee populates EmployeeName and EmployeeDept from DB. So, it's working fine. Now I want to save from the comboboxes depending what user select. Now,
How can I get the selected item from each combobox and save in a Class(dummyClass) object String(SelectedEmployeeName, SelectedEmployeeDept).
I've tried something like this after searching for right solution what I really want, most of the solutions are for datagridview, not datagrid:
Dim seletedCB = DgvEmployee.Columns(1).GetCellContent(DgvEmployee.Items(0)) MsgBox(seletedCB.ToString())
But it's giving me output as System.Windows.Controls.ContentPresenter.
I also have checked with debugging selectedCB doesn't contain selecteditem. What am I doing wrong? How can I achieve the selected item?
I am really stuck here. Your contribution would be life saving. Thanks a lot in advance.

WPF DataGrid Radio Button Binding Issue

I am having RadioButton binding issue.Below is my xaml code.
<Grid Style="{DynamicResource MainFrameGrid1}">
<Grid Name="grdPCM">
<DataGrid AutoGenerateColumns="False" Height="407" HorizontalAlignment="Left" Margin="18,102,0,0" Name="dgMI" VerticalAlignment="Top" Width="781"
CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserResizeColumns="True" CanUserSortColumns="False" SelectionMode="Single"
SelectionUnit="Cell" EnableColumnVirtualization="True">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<RadioButton Name="rbM" GroupName="CMGrp" IsChecked="{Binding Path=SELECT, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
In code behind I am doing like this.
I am getting data from the database and storing it in the DataTable "dtMIData".Then I am temporarily adding a boolean column called "SELECT" and assigning it to data grid like below in code behind.
DataColumn dcRB = new DataColumn("SELECT");
dcRB.DataType = Type.GetType("System.Boolean");
dcRB.DefaultValue = false;
dtMIData.Columns.Add(dcRB);
dtMIData.AcceptChanges();
dgMI.ItemsSource = dtMIData.AsDataView();
I am binding this new column "SELECT" with the RadioButton control.I want to select only one record,out of multiple retrieved records and want to retrieve the selected record in the code behind based on the "SELECT" column value.i.e."SELECT" column value is true.But even though RadioButton is checked,required binding column "SELECT" is not updating with value true.How to reflect the checked state in the related binding column?
In a DataGrid the bindings do not get updated until you end editing of the row. Try adding UpdateSourceTrigger=PropertyChanged to the RadioButton's binding expression to update bindings immediately:
<RadioButton Name="rbM" GroupName="CMGrp" IsChecked="{Binding Path=SELECT, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Binding ComboBox to DataGrid Entry

Solved problem, but still have questions. See end of post, or read on for context.
I'm trying to setup a WPF datagrid which has many templated columns with comboboxes inside of them. In particular I'm having trouble with data-binding.
Data Model
First, I have Entry, which contains 4 properties:
Name
Customer
Color
Type
The most interesting property is Color which has two more sub properties:
ColorString
Index
Goal
I need to create a data grid where each row corresponds to an Entry object. The Customer, Color, and Type properties all have comboboxes that allow for a selection of dynamically populated choices. I need the selected item for each combobox to bind to the entry's respective property.
Screenshot
Questions:
How do I properly set the data contexts for the data grid and for each combobox?
For the data grid, I'm setting the data context programmatically to an instance of ObservableCollection.
private ObservableCollection<Entry> entries = new ObservableCollection<Entry>();
public MainWindow()
{
InitializeComponent();
entries.Add(new Entry("Foo", "Customer1", new MyColor("#00000000", 1), "Type1"));
entries.Add(new Entry("Bar", "Customer2", new MyColor("#00000000", 1), "Type2"));
LayerMapGrid.DataContext = entries; //Set data-context of datagrid
}
For the color combobox, I'm using an ObjectDataProvider in my XAML:
<Window.Resources>
<ObjectDataProvider x:Key="Colors" ObjectType="{x:Type local:MyColor}" MethodName="GetColors"/>
</Window.Resources>
This is how I bind the ObjectDataProvider
<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}"/>
In the MyColor class, I've created the method below:
public static ObservableCollection<MyColor> GetColors()
{
ObservableCollection<MyColor> colors = new ObservableCollection<MyColor>();
//... Fill collection... (omitted for brevity)
return colors;
}
The good news is that ALL of the above code WORKS. However is it a good approach to the problem at hand?
This my next and more important question:
How do I bind the selected items of the comboboxes so that ObservableCollection<Entry> is updated?
I'm aware that I need to set the UpdateSourceTrigger="PropertyChanged", so that my source, which is the Entry collection is updated.
Here is my current XAML code for setting up my entire data grid. Note: I haven't implemented the customer and type comboboxes yet. I'm really just concerned with the color combobox:
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="LayerMapGrid">
<DataGrid.Columns>
<!--Name Column-->
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Control.HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
</DataGridTemplateColumn>
<!--Customer Column-->
<DataGridTemplateColumn Header="Customer">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding CustomerName, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--Color Column-->
<DataGridTemplateColumn Header="Color">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}" SelectedItem="{Binding Color, ElementName=LayerMapGrid, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<DockPanel Margin="2">
<Border DockPanel.Dock="Left" HorizontalAlignment="Right" BorderThickness="1" BorderBrush="Black" Margin="1" Width="10" Height="10">
<Rectangle Name="ColorRec" Fill="{Binding ColorString}"/>
</Border>
<TextBlock Padding="4,2,2,2" Text="{Binding ColorString}" VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--Type Column-->
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Type, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Your assistance is greatly appreciated. I've been banging my head on the wall for about 16 hours with this.
-Nick Miller
EDIT:
I found the solution (and as always immediately after requesting help), but I don't understand how it works.
In the XAML, I've changed the combobox binding to be the following:
<ComboBox ItemsSource="{Binding Source={StaticResource Colors}}" SelectedItem="{Binding Color, UpdateSourceTrigger=PropertyChanged}">
What exactly is happening here?
Why is the combobox now referring to the datagrid's data context? Doesn't that get overriden when I set the ItemsSource to point to my ObjectDataProvider?
How do I properly set the data contexts for the data grid and for each combobox?
You don't. Normally in WPF, we set the DataContext property on the UserControl, or Window that we are designing, not on individual controls. In this way, all the controls in the view have access to the data properties.
How do I bind the selected items of the comboboxes so that ObservableCollection is updated?
Again, you don't do what you are doing. Rather than using an ObjectDataProvider, you should just have a property of type ObservableCollection<MyColor> in the code behind and bind to that directly. And you should be binding a public Entries property to the DataGrid.ItemsSource property, not setting the private entries field as the DataContext. You really need to read through the Data Binding Overview page on MSDN for further help with this.
Set the DataContext of MainWindow.xaml to itself (which generally is a not a good idea):
public MainWindow()
{
InitializeComponent();
Entries.Add(new Entry("Foo", "Customer1", new MyColor("#00000000", 1), "Type1"));
Entries.Add(new Entry("Bar", "Customer2", new MyColor("#00000000", 1), "Type2"));
DataContext = this; //Set DataContext to itself so you can access properties here
}
Then in MainWindow.xaml:
<DataGrid ItemsSource="{Binding Entries}" ... />
The DataTemplates in each row of the DataGrid automatically have the relevant item in the data bound collection set as the DataContext, so you automatically get access to the properties from the relevant class. Again... no need to set any DataContexts manually. If you have further questions, please read the linked article, because the answers are there.
UPDATE >>>
Let's be clear about something... I am not your personal mentor, so this will be my last reply.
How do I overcome the combobox inheriting the data context of the data grid?
To data bind to the object that is set as the Window.DataContext, you just need to use a valid Binding Path and some basic logic:
<ComboBox ItemsSource="{Binding DataContext.MyColors, RelativeSource={RelativeSource
AncestorType={x:Type YourLocalPrefix:MainWindow}}}" />

WPF Datagrid with databinding, changing ItemsSource

I've got a datagrid in WPF that shows a grid of some data. The data is retrieved form a ViewModel, which contains the following properties:
Public ReadOnly Property Devices() As List(Of Device)
Get
Return FDevices
End Get
.
Public ReadOnly Property ClientNetworks() As List(Of network)
Get
Return fnetwork
End Get
End Property
Both properties are filled with data after constructing the view model.
To use the properties in the Datagrid i use the following XAML.
<DataGrid ItemsSource="{Binding Devices}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Customer" >
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
------------------ <TextBlock Text="{Binding ClientNetwork.Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
------------------> <ComboBox ItemsSource="{Binding ClientNetwork}" DisplayMemberPath="Description"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
This should show a textbox with the description and a combobox with strings when edited.
Outside of the Datagrid the combobox works fine. I know this is because of the set ItemsSource on the Datagrid but i can't seem to find how to make it work. i've tried several alterations of the combobox code but none have worked so far.
The goal is to make the user be able to edit the cell and have a combobox presented, from which he can select an string, then the corresponding int will be saved in the database.
UPDATE 1
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.ClientNetworks}"
DisplayMemberPath="Description"
SelectedItem="{Binding ClientNetwork}"
/>
This is how i fixed the resting of the datacontext
I found a way to get it done but i am not sure this is the way it should be done
<Window.Resources>
<CollectionViewSource Source="{Binding ClientNetworks}" x:Key="clientnetworks" />
</Window.Resources>
and in the combobox
<ComboBox ItemsSource="{Binding Source={StaticResource clientnetworks}}" DisplayMemberPath="Description" />

DataGrid ComboBox Binding Issue with Selected or New Item

I am having issue with retaining selected item in a DataGridTemplate column ComboBox.
I have the DataTemplate editable combobox column as the first column in the datagrid and next to it, I have a text column. The DataGrid is populated with data read from SQL stored procedure. Everything works fine, except when I select an item in the combobox and move to the text field and start typing in it, the Combo selection blanks out. It blanks out both for a new item or existing item. Oddly, this happens only the first time. When I reselect the ComboBox Value or add the new item again and go back to the text field, it does not blank out. I am running out of ideas and tried many combinations, but no luck so far.
Here is my code:
This is how I am populating the DataGrid:
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandText = "GetProducts";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = sqlConn;
var reader = cmd.ExecuteReader();
var dt = new DataTable();
dt.Load(reader);
dt.Columns["ProductName"].AllowDBNull = true;
dtProductCfgTable = dt;
ProductCfgGrid.ItemsSource = dtProductCfgTable.DefaultView;
}
This is the declaration for ProductNamesList:
public List<string> ProductNamesList { get; set; }
XAML:
<DataGridTemplateColumn Header="ProductName">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ProductNamesList,
RelativeSource={RelativeSource AncestorType=Window}}"
SelectedItem="{Binding ProductName
IsSynchronizedWithCurrentItem="False"
BorderThickness="1.2 1.2 0 0" BorderBrush="Black"
Background="LightCyan" IsEditable="True" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding ShippingAddress}"
Width="100"
Header="ShippingAddress"
Visibility="Visible"/>
The reason the data is lost is because the CellTemplate only provides non-edit features, so whenever you changed a value in the combobox when editing a new row, the data wasn't getting set because there was no edit-mode implementation, so no object was getting created behind the scenes. DatagridTextColumn automatically has editing build into it, which is why the combobox would work after editing this type of cell.
<DataGridTemplateColumn Header="ProductName" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ProductNamesList,
RelativeSource={RelativeSource AncestorType=Window}}"
SelectedValue="{Binding ProductName, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
IsEditable="False"
IsHitTestVisible="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ProductNamesList,
RelativeSource={RelativeSource AncestorType=Window}}"
Text="{Binding ProductName, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
IsEditable="True" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
The redundancy in comboboxes is only necessary if you want the user to see a combobox when in non-edit mode. If you don't care about that you can simply write:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>

Resources