Issue binding datatable to WPF datagrid - wpf

My XAML is as follows:
<DataGrid x:Name="WaterfallDataGrid" HorizontalAlignment="Left" Height="540" Margin="10,410,0,0" VerticalAlignment="Top" Width="1650" CanUserSortColumns="False" ColumnWidth="60">
<DataGrid.Columns>
<DataGridTextColumn Header="Load" Binding="{Binding Load}"></DataGridTextColumn>
<DataGridTextColumn Header="PF" Binding="{Binding PF}"></DataGridTextColumn>
<DataGridTextColumn Header="Spare" Binding="{Binding Spare}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
My VB.net code is as follows:
Dim dt3 As New DataTable("Waterfall")
dt3.Columns.Add("Load")
dt3.Columns.Add("PF")
dt3.Columns.Add("Spare")
dt3.rows.add(New Objecet() {"full load", "0.8", "20%"})
WaterfallDataGrid.itemSource = dt3.defaultview
While the datatable does get displayed on the datagrid, it creates another 3 new columns (Load, PF and Spar) in addition to the original 3 columns I created in XAML.
How do I bind the VB.net code to the XAML datagrid?
Here is the screenshot following Ed's code
Here is the code that I modify following Mark's comment.
<Window x:Class="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:Datagrid_Binding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="WaterfallDataGrid" HorizontalAlignment="Left" Height="170" Margin="85,65,0,0" VerticalAlignment="Top" Width="340" AutoGenerateColumns="False"/>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Height="25" Margin="380,255,0,0" VerticalAlignment="Top" Width="45"/>
</Grid>
</Window>
And this is the VB code
Class MainWindow
Private Sub button_Click(sender As Object, e As RoutedEventArgs) Handles button.Click
Dim dt3 As New DataTable("Waterfall")
dt3.Columns.Add("Load")
dt3.Columns.Add("PF")
dt3.Columns.Add("Spare")
'dt3.rows.add(New Object() {"full load", "0.8", "20%"})
dt3.Rows.Add("full load", "0.8", "20%")
WaterfallDataGrid.ItemsSource = dt3.DefaultView
End Sub
End Class
The datagrid does not show any data at all. It shows a collapsed row somehow. Screenshot as follows:
I think the issue is that if I don't pre-defined the columns in XAML and I defined the rows in vb.net but yet the autogeneratecolumn is set to false, there is no way for VB to programmatically create the columns during runtime so what I am seeing here is a collapsed row.
I think the approach would be to pre-defined the columns in XAML and then bind the data to the XAML columns but none of the codes work....really frustrating.

I think that perhaps the screenshot in the question doesn't relate to the code shown. If the only issue with your initial code was the extra columns being generated, you just need to add AutoGenerateColumns="False" to the DataGrid XAML:
<DataGrid x:Name="WaterfallDataGrid"
HorizontalAlignment="Left"
Height="540"
Margin="10,410,0,0"
VerticalAlignment="Top"
Width="1650"
CanUserSortColumns="False"
ColumnWidth="60"
AutoGenerateColumns="False">
Also, since DataRowCollection.Add takes a ParamArray argument, you don't need to create the Object array and can just pass in the column values individually, which is a bit easier to read, e.g.
dt3.Rows.Add("full load", "0.8", "20%")

When you set the AutoGenerateColumns property of the DataGrid to False you need to explitly define the columns yourself so use your original XAML markup with the addition of AutoGenerateColumns="False" and you should see the three columns:
<DataGrid x:Name="WaterfallDataGrid" HorizontalAlignment="Left" Height="540" Margin="10,410,0,0" VerticalAlignment="Top" Width="1650" CanUserSortColumns="False" ColumnWidth="60"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Load" Binding="{Binding Load}"></DataGridTextColumn>
<DataGridTextColumn Header="PF" Binding="{Binding PF}"></DataGridTextColumn>
<DataGridTextColumn Header="Spare" Binding="{Binding Spare}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
To get rid of the extra blank row that is there for the user to be able to add a new row, you could set the CanUserAddRows property to False:
<DataGrid x:Name="WaterfallDataGrid" CanUserAddRows="False" ...
Yes I am aware of that. The problem is that whilst I can create 3 columns in XAML, I am unable to bind the data to the column via my VB code above.
The following complete code sample does work as expected for me.
MainWindow.xaml.vb:
Class MainWindow
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Dim dt3 As New DataTable("Waterfall")
dt3.Columns.Add("Load")
dt3.Columns.Add("PF")
dt3.Columns.Add("Spare")
'dt3.rows.add(New Object() {"full load", "0.8", "20%"})
dt3.Rows.Add("full load", "0.8", "20%")
WaterfallDataGrid.ItemsSource = dt3.DefaultView
End Sub
End Class
MainWindow.xaml:
<Window
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:WpfApplicationVb1"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero2" x:Class="MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="WaterfallDataGrid" CanUserSortColumns="False" ColumnWidth="60" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Load" Binding="{Binding Load}"></DataGridTextColumn>
<DataGridTextColumn Header="PF" Binding="{Binding PF}"></DataGridTextColumn>
<DataGridTextColumn Header="Spare" Binding="{Binding Spare}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

Related

WPF datagrid getting selected combobox value from selected row (Powershell)

So maybe I am approaching this the wrong way, feel free to give any advice or tips.
Currently working with a DataGrid table in wpf using powershell to do all the logic. Here is the table:
What is the best way to properly extract all the data INCLUDING the combobox selected value from a selected row?
Currently I am trying this:
$test = $AddServerwpf.ServerGrid.SelectedItems[0]
Write-Host "Selected: $test"
For context $AddServerwpf contains all the objects for that window. ServerGrid is the DataGrid object. I am using .SelectedItems[0] to get that row.
This returns:
Selected: #{Server=server1; Environment=Prod; ServiceAccount=System.Object[]}
If I go one step further with:
$AddServerwpf.ServerGrid.SelectedItems[0].ServiceAccount
I get:
Selected: account1 account2 account3
This obviously doesn't tell me which account was selected from the drop down. How can I get the combobox selection? I have looked pretty deeply through google with other questions about this and I have not found a working answer. How do I properly bind the combobox to the datagrid? Or is it better to get to the Combobox object somehow and extract the text?
Here is the XAML:
<Window x:Class="ServerManagmentApp.AddServer" x:Name="AddServerWindow"
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:ServerManagmentApp"
mc:Ignorable="d"
Title="AddServer" Height="359.7" Width="387.4" Background="#FF2B2929">
<Grid>
<Button x:Name="AddServerButton" Content="Add Server" HorizontalAlignment="Left" Margin="220,260,0,0" VerticalAlignment="Top" Width="120" Height="40" Background="#FF1FD14F"/>
<DataGrid x:Name="ServerGrid" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="213" Width="275" Background="#FF888F8A" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Header="Server" Binding="{Binding Server}" Width="*" />
<DataGridTextColumn Header="Environment" Binding="{Binding Environment}" Width="*" />
<DataGridTemplateColumn Header="ServiceAccount" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="ServiceAccount" ItemsSource="{Binding ServiceAccount}" SelectedIndex="0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Spent a couple hours trying to get past this roadblock and any help would be appreciated. I am new to this and trying to wrap my head around how this binding works while using powershell.
EDIT:
Here is how I am populating the DataGrid if that helps
$list = "account1","account2","account3"
$AddServerwpf.ServerGrid.AddChild([pscustomobject{Server='server1';Environment='Prod';ServiceAccount=$list})
$AddServerwpf.ServerGrid.AddChild([pscustomobject]#{Server='server2';Environment='Prod';ServiceAccount=$list})
$AddServerwpf.ServerGrid.AddChild([pscustomobject]#{Server='server3';Environment='Prod';ServiceAccount=$list})
You should bind the SelectedItem property of the ComboBox to a property of your data object where the Server, Environment and ServiceAccount properties are already defined:
<ComboBox x:Name="ServiceAccount" ItemsSource="{Binding ServiceAccount}"
SelectedItem={Binding SelectedAccount, UpdateSourceTrigger=PropertyChanged}" />
$list = "account1","account2","account3"
$AddServerwpf.ServerGrid.AddChild([pscustomobject{Server='server1';Environment='Prod';ServiceAccount=$list;SelectedAccount='account1'})
$AddServerwpf.ServerGrid.AddChild([pscustomobject]#{Server='server2';Environment='Prod';ServiceAccount=$list;SelectedAccount='account1'})
$AddServerwpf.ServerGrid.AddChild([pscustomobject]#{Server='server3';Environment='Prod';ServiceAccount=$list;SelectedAccount='account1'})
You can then get the selected value of an item using this property, e.g.:
$AddServerwpf.ServerGrid.SelectedItems[0].SelectedAccount

the type DataGrid doesn't support direct Content

I am xaml and c# beginner i am across a situation in silverlight application that i got this error i am trying to create columns using xaml (i dont have to use c# or anything dynamic).
here is my code:
<UserControl x:Class="DEV_CENTER.ProgramGrid"
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:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:dataprimitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:ProgramViewModel x:Key="ProgramViewModel"/>
</UserControl.Resources>
<data:DataGrid x:Name="gridPrograms"
Grid.Row="0"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=ListPrograms }"
IsReadOnly="True"
DataContext="{StaticResource ProgramViewModel}">
<data:DataGridTextColumn Header="c1"
Binding="{Binding Version}"
Width="2*"/>
<data:DataGridTextColumn Header="c2"
Binding="{Binding Live}"
Width="2*"/>
<data:DataGridTextColumn Header="c3"
Binding="{Binding DateCreation}"
Width="2*"/>
<data:DataGridTextColumn Header="..." Width="*"/>
</data:DataGrid.Columns>
</data:DataGrid>
</UserControl>
And i get error : the type DataGrid doesn't support direct Content
EDIT:The problem was due to missing <data:DataGrid.Columns> that is solved but second problem is how to bind the column ?
Could someone please give me the solution ? I mean i have to create columns using xaml only (not c#) and bind them. How to do that ?
thanks
The message means you are not allowed to put anything between the opening and closing tags of the DataGrid as long as you are not "addressing" a DataGrid property, you are missing the opening tag <data:DataGrid.Columns>:
<data:DataGrid ...>
<data:DataGrid.Columns>
...
</data:DataGrid.Columns>
</data:DataGrid>

ReadWrite & ReadOnly datagrid with binding to shared source, resulting in ReadWrite not proper ReadWrite?

I found something strange:
I have a form with two datagrids with binding to the same collection.
Depending on the order of the datagrids in the Xaml the behavior is different.
This works as expected (the extra row for adding is present):
<DockPanel>
<DockPanel DockPanel.Dock="Right">
<Label Content="ReadOnlyView" DockPanel.Dock="Top"/>
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="True" />
</DockPanel>
<DockPanel>
<Label Content="EditorView" DockPanel.Dock="Top" />
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="False" CanUserAddRows="True" />
</DockPanel>
</DockPanel>
Arranging the xaml this way is what confuses me (no extra row for adding)
<DockPanel>
<DockPanel>
<Label Content="EditorView" DockPanel.Dock="Top" />
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="False" CanUserAddRows="True" />
</DockPanel>
<DockPanel DockPanel.Dock="Right">
<Label Content="ReadOnlyView" DockPanel.Dock="Top"/>
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="True" />
</DockPanel>
</DockPanel>
Below is the dummy ViewModel I used for this:
public class PersonsViewModel
{
public PersonsViewModel()
{
Persons = new ObservableCollection<Person>
{
new Person {Name = "Johan"},
new Person {Name = "Dave"},
};
}
public ObservableCollection<Person> Persons { get; private set; }
}
public class Person
{
public string Name { get; set; }
}
My question is what is the reason for this behavior?
An excellent question Johan! My guess would be that since you are not explicitly providing it with a CollectionViewSource, the automatically generated cvs by DataGrid is shared between the two as you are referring to the same source.
Hence the last setting wins when you issue two IsReadOnly assignments and being a shared source, both DataGrid show you the same effect.
In order to confirm my guess I've used this code and the DataGrids behave as you would expect when you offer them explicit CollectionViewSource to work with.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource Source="{Binding Persons}" x:Key="cvs1" />
<CollectionViewSource Source="{Binding Persons}" x:Key="cvs2" />
</Window.Resources>
<DockPanel>
<DataGrid ItemsSource="{Binding Source={StaticResource ResourceKey=cvs1}}" IsReadOnly="False" CanUserAddRows="True" />
<DataGrid ItemsSource="{Binding Source={StaticResource ResourceKey=cvs2}}" IsReadOnly="True" />
</DockPanel>
</Window>
EDIT: Further testing indicates the behaviour can just be described as odd! I cannot explain why this produces three readonly DGs
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="True" />
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="False" />
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="True" />
but this produces alternating readonly and editable DG:
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="True" />
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="False" />
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="True" />
<DataGrid ItemsSource="{Binding Persons}" IsReadOnly="False" />
So I guess the above CVS is best described as workaround for this odd behaviour, so you can achieve what you actually want.
EDIT 2: After even more combinations of true false, the only consistent thing i've noticed is that if the Last IsReadOnly in DataGrid is set to True, all other DataGrids become readonly. But if the last one is set to false, then all other DataGrids behave according to their own IsReadOnly setting. This behvaiour may possibly be due to this MSDN bit
If a conflict exists between the settings at the DataGrid, column,
or cell levels, a value of true takes precedence over a value of false.

DataGrid ColumnWidth Property ignored in DataTemplate for row details

This is the UserControl that displays the details from my application and as you can see the ColumnWidth Property is explicit set to *. I also tried to set the Width property from the DataGridTextColumn.
<UserControl x:Class="WpfUserInterface.MyDetailsView"
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" Height="185" d:DesignWidth="480">
<Grid>
<DataGrid ColumnWidth="*" Margin="10">
<DataGrid.Columns>
<DataGridTextColumn Header="Column1"/>
<DataGridTextColumn Header="Column2"/>
<DataGridTextColumn Header="Column3"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
This is the main window that only contains the DataGrid.
<Window x:Class="WpfUserInterface.Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window" Height="306" Width="453">
<Grid>
<DataGrid ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn Header="ParentColumn1"/>
<DataGridTextColumn Header="ParentColumn2"/>
<DataGridTextColumn Header="ParentColumn3"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<MyDetailsView/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
This is what shows up on the screen when you run the application and select a row in the parent DataGrid.
When I set the width of the DataGrid in the MyDetailsView to a specified value like 400 the columns are sized perfect but this is not an option. Is there any way to solve this problem? A workaround?
I know this question was asked about a year ago, but I've been facing the same problem and I've found out this solution:
this.dgrData.Columns[0].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
this.dgrData.Columns[1].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
...
Hope it may be of any help to someone.

Why is WPFToolkit DataGrid so slow when binding?

I have a very simple test application where I have two objects, each with a small collection of items. when I select an object I display its collection in a WPFToolkit DataGrid.
The problem is there is a noticeable delay, such that if you press up/down keys to toggle selection between objects you can see it can't keep up.
Why is the performance so bad?
<Window x:Class="SlowGridBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Shops}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
<Controls:DataGrid ItemsSource="{Binding Shops/Vegetables}" AutoGenerateColumns="True"/>
</StackPanel>
The DataContext is populated with some test classes filled with 50 items of random test data.
Change the attribute AutoGenerateColumns="True" to AutoGenerateColumns="False" and define your columns for the datagrid:
<my:DataGrid AutoGenerateColumns="False" ... >
<my:DataGrid.Columns>
<my:DataGridTextColumn Header="Col1" Width="*" Binding="{Binding Path=Col1}" />
<my:DataGridTextColumn Header="Col2" Width="*" Binding="{Binding Path=Col2}" />
.
.
.
</my:DataGrid.Columns>
</my:DataGrid>
This is what fixed the performance issues for me.

Resources