WPF Datagrid autogenerated columns - wpf

I have bound a datatable to a datagrid in WPF. Now on clicking a row in the grid I need to have a window pop up. But for that, I need to first change a column in the datagrid to be a hyperlink. Any ideas on how to do that?
<DataGrid Name="dgStep3Details" Grid.Column="1" Margin="8,39,7,8" IsReadOnly="True" ItemsSource="{Binding Mode=OneWay, ElementName=step3Window,Path=dsDetails}" />
If I can't change an autogenerated column to hyperlink, is there a way to add a button to each row instead?
Thanks
Nikhil

So, it was really hard to create hyperlink columns to autogenerated datagrid. What I eventually did was this - create buttons to the grid on the fly and then attach a routed event for the same based on the autogenerate event of the datagrid where I shall put my code. I didn't want my code to be hardcoded to the columns and now I'm flexible by changing the datatable on the fly. Here is the code:
private void dgStep3Details_AutoGeneratedColumns(object sender, EventArgs e)
{
DataGrid grid = sender as DataGrid;
if (grid == null)
return;
DataGridTemplateColumn col = new DataGridTemplateColumn();
col.Header = "More Details";
FrameworkElementFactory myButton = new FrameworkElementFactory(typeof(Button), "btnMoreDetails");
myButton.SetValue(Button.ContentProperty, "Details");
myButton.AddHandler(Button.ClickEvent, new RoutedEventHandler(btnMoreDetails_Click));
DataTemplate cellTempl = new DataTemplate();
//myButton.SetValue(Button.CommandParameterProperty, ((System.Data.DataRowView)((dgStep3Details.Items).CurrentItem)).Row.ItemArray[0]);
cellTempl.VisualTree = myButton;
col.CellTemplate = cellTempl;
dgStep3Details.Columns.Add(col);
}
public void btnMoreDetails_Click(object sender, RoutedEventArgs e)
{
//Button scrButton = e.Source as Button;
string currentDetailsKey = ((System.Data.DataRowView)(dgStep3Details.Items[dgStep3Details.SelectedIndex])).Row.ItemArray[0].ToString();
// Pass the details key to the new window
}

I don't think you'll be able to get these advanced UI features out of autogenerated columns. I think you'll either have to decide to program these columns in C# or VB.NET when you retrieve your data and tailor them the way you like, or you'll have to abandon the UI ideas you've mentioned. Autogenerated columns just cannot do that.
However, you could change your approach. Try checking into events like MouseLeftButtonDown, etc. and see if you can simulate the behavior you want by other means.

Related

How to scroll to a given row when WPF DataGrid is loaded

I display a few thousand rows in a WPF DataGrid, but would like to automatically scroll to a specific row when it is loaded (and later also add a button to scroll to this specific row). Is it possible to achieve it via data binding or from a view model property?
Of course, I've already browsed the Internet, but could not find any corresponding property.. For exemple, SelectedCells only has a get accessor (but I don't think it would have scrolled whatsoever anyway). I don't think that the other properties, such as SelectedItem helps.
Thanks for any insights :-)
There are two options: scroll to a row without selecting it and scroll to a row and select it.
Scroll to row without selecting it
This solution works for any DataGrid.SelectionUnit value (cell or row based selection).
You set the row from a property in your view model, which binds to DataGrid.CurrentItem.
Scrolling must be handled by the DataGrid by handling the DataGrid.CurrentCellChanged event.
View model (must implement INotifyPropertyChanged)
object currentRow = this.DataSource[currentRowIndex];
this.CurrentRow = currentRow;
View.xaml
<DataGrid ItemsSource="{Binding DataSource}"
CurrentItem="{Binding CurrentRow}"
CurrentCellChanged="DataGrid_OnCurrentCellChanged" />
View.xaml.cs
private void DataGrid_OnCurrentCellChanged(object sender, EventArgs eventArgs)
{
var dataGrid = sender as DataGrid;
dataGrid.ScrollIntoView(dataGrid.CurrentItem);
}
Scroll to row by selecting it
This solution works only for DataGrid.SelectionUnit values DataGridSelectionUnit.FullRow or DataGridSelectionUnit.CellOrRowHeader (not cell-only based selection - cell-only solution must completely handle selection and scroll in view).
You set the selected row from a property in your view model, which binds to DataGrid.SelectedItem.
Scrolling must be handled by the DataGrid by handling the DataGrid.SelectionChanged event.
View model (must implement INotifyPropertyChanged)
object selectedRow = this.DataSource[currentRowIndex];
this.SelectedRow = selectedRow;
View.xaml
<DataGrid ItemsSource="{Binding DataSource}"
SelecetedItem="{Binding SelectedRow}"
SelectionChanged="DataGrid_OnCurrentCellChanged" />
View.xaml.cs
private void DataGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs eventArgs)
{
var dataGrid = sender as DataGrid;
dataGrid.ScrollIntoView(dataGrid.SelectedItem);
}

How can i get the clicked button's row values on silverlight telerik radgridview button column?

I have a hyperlink button colum in silverlight telerik radgridview, as a cell template.
I am using MVVM.
When i click on the hyperlink button, how can i get its row's values(atleast in the code behind)..?
Thanks in advance..
You need to use the ParentOfType extension method to get hold of the row containing the button.
Once you have the row you can cast the item type to your viewmodel and access the properties of it.
Make sure you have a using statement for the extension method:
using Telerik.Windows.Controls;
private void Button_Click(object sender, RoutedEventArgs e)
{
var parent = (sender as Button).ParentOfType<GridViewRow>();
var rowVM = parent.Item as YourVmClass;
}
If you are using MVVM you can also use the EventToCommandBehavior.
This allows you to execute all events as commands. You can then handle the command in your viewmodel.

How to force DataGrid to rebuild VisualTree for its columns

I have WPF form with DataGrid. New columns can be added to the datagrid manually by user via button. This is the code to add new column:
private void ColumnAdornerAddButton_MouseDown(object sender, MouseButtonEventArgs e)
{
DataGridTextAdornerColumn column = new DataGridTextAdornerColumn();
column.Header = "New column";
column.HeaderStyle = (Style)FindResource("columnHeader");
column.AdornerTemplate = (DataTemplate)FindResource("columnAdorner");
Binding binding = new Binding("Data");
binding.Mode = BindingMode.TwoWay;
column.Binding = binding;
grid.Columns.Insert(grid.Columns.Count - 1, column);
//Add adorner
DataGridColumnHeader header = GetColumnHeaderFromColumn(column);
AddAdorner(header, column.AdornerTemplate, column.IsReadOnly);
}
private DataGridColumnHeader GetColumnHeaderFromColumn(DataGridColumn column)
{
// dataGrid is the name of your DataGrid. In this case Name="dataGrid"
List<DataGridColumnHeader> columnHeaders = GetVisualChildCollection<DataGridColumnHeader>(grid);
foreach (DataGridColumnHeader columnHeader in columnHeaders)
{
if (columnHeader.Column == column)
{
return columnHeader;
}
}
return null;
}
The problem is that after I have added the column to the grid its header is not yet generated and it is not present in visual tree. Thus I cant get header for the new column and apply adorner to it.
I have tried to call ApplyTemplate recursively on visual tree of the grid without any luck.
Is there any way to force grid to generate DataGridColumnHeader for the new column in code?
Thank you in advance.
Hi after adding columns to datagrid call the method UpdateLayOut() of DataGrid.
datagrid.UpdateLayout();
I hope this will help.
I just want to enhance the solution,
The method
datagrid.Items.Refresh();
will helps to recreate the view (Datagrid).
thus you can see the updated value in datagrid

Programmatically set content in Silverlight DataGrid details

I need to dynamically set the contents within the template of a DataGrid based on information in an external settings file. That settings file specifies which data fields should display in the DataGrid. The administrator of the application can edit the settings to change the fields to display. I cannot hard-code the fields to display.
I can easily add the columns (DataGridTextColumn's) to the DataGrid at runtime. I set a binding to a field in the item source based on the settings, and that displays fine.
Now I need to display details when the user clicks a row. I set up a RowDetailsTemplate with DataTemplate, and added a Grid (or a StackPanel) inside to format the details. If I add to the markup the TextBlocks with bindings to fields, it displays the details just fine.
But how can I set the content of the Grid/StackPanel in the details template programmatically? The Grid/StackPanel controls are null if I try to reference them by name on startup (e.g., in the page Loaded event). I have tried using the Loaded event on the Grid/StackPanel to add the details. That code runs and appears to add the content to the Grid/StackPanel, but nothing actually appears when I click the row. I'm guessing that the problem is that the template/Grid is already loaded and ignores the changes I'm making.
Here's a sample of the code I'm using in the handler for the Loaded event. Even if I do something as simple as this, the details pane doesn't appear when clicking on the row.
<data:DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border Background="LightBlue" >
<StackPanel x:Name="resultsDetailsPanel"
Orientation="Vertical"
Loaded="resultsDetailsPanel_Loaded">
</StackPanel>
</Border>
</DataTemplate>
</data:DataGrid.RowDetailsTemplate>
private void resultsDetailsPanel_Loaded(object sender, RoutedEventArgs e)
{
if (_resultsGridLoaded)
return;
StackPanel detailsPanel = sender as StackPanel;
TextBlock fieldNameTextBlock = new TextBlock();
fieldNameTextBlock.Text = "TESTING";
detailsPanel.Children.Add(fieldNameTextBlock);
_resultsGridLoaded = true;
}
I actually tried your code and is working. Two things you should check:
Is your _resultsGridLoaded variable initialized as false?
Did you set RowDetailsVisibilityMode="VisibleWhenSelected" on your DataGrid?
UPDATE: For some reason is not working anymore. But I did found two ways you can fix it:
Remove the resultsGridLoaded logic.
If you need that logic, you can add a handler for the SelectionChanged event on the DataGrid, in there you can set the _resultsGridLoaded variable to false so the new StackPanel gets its content added correctly:
And the code behind:
private void resultsPanel_Loaded(object sender, RoutedEventArgs e)
{
if (_resultsGridLoaded)
return;
StackPanel pane = (StackPanel)sender;
TextBlock newChild = new TextBlock()
{
Text = "New text"
};
pane.Children.Add(newChild);
_resultsGridLoaded = true;
}
private void grid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_resultsGridLoaded = false;
}
Hope this helps

Create WPF ItemTemplate DYNAMICALLY at runtime

At run time I want to dynamically build grid columns (or another display layout) in a WPF ListView. I do not know the number and names of the columns before hand.
I want to be able to do:
MyListView.ItemSource = MyDataset;
MyListView.CreateColumns();
You can add columns dynamically to a ListView by using Attached Properties. Check out this article on the CodeProject it explains exactly that...
WPF DynamicListView - Binding to a DataMatrix
From MSDN:
MyListBox.ItemsSource = view;
ListView myListView = new ListView();
GridView myGridView = new GridView();
myGridView.AllowsColumnReorder = true;
myGridView.ColumnHeaderToolTip = "Employee Information";
GridViewColumn gvc1 = new GridViewColumn();
gvc1.DisplayMemberBinding = new Binding("FirstName");
gvc1.Header = "FirstName";
gvc1.Width = 100;
myGridView.Columns.Add(gvc1);
GridViewColumn gvc2 = new GridViewColumn();
gvc2.DisplayMemberBinding = new Binding("LastName");
gvc2.Header = "Last Name";
gvc2.Width = 100;
myGridView.Columns.Add(gvc2);
GridViewColumn gvc3 = new GridViewColumn();
gvc3.DisplayMemberBinding = new Binding("EmployeeNumber");
gvc3.Header = "Employee No.";
gvc3.Width = 100;
myGridView.Columns.Add(gvc3);
//ItemsSource is ObservableCollection of EmployeeInfo objects
myListView.ItemsSource = new myEmployees();
myListView.View = myGridView;
myStackPanel.Children.Add(myListView);
i'd try following approach:
A) you need to have the list box display grid view - i believe this you've done already
B) define a style for GridViewColumnHeader:
<Style TargetType="{x:Type GridViewColumnHeader}" x:Key="gridViewColumnStyle">
<EventSetter Event="Click" Handler="OnHeaderClicked"/>
<EventSetter Event="Loaded" Handler="OnHeaderLoaded"/>
</Style>
in my case, i had a whole bunch of other properties set, but in the basic scenario - you'd need Loaded event. Clicked - this is useful if you want to add sorting and filtering functionality.
C) in your listview code, bind the template with your gridview:
public MyListView()
{
InitializeComponent();
GridView gridViewHeader = this.listView.View as GridView;
System.Diagnostics.Debug.Assert(gridViewHeader != null, "Expected ListView.View should be GridView");
if (null != gridViewHeader)
{
gridViewHeader.ColumnHeaderContainerStyle = (Style)this.FindResource("gridViewColumnStyle");
}
}
D) then in you OnHeaderLoaded handler, you can set a proper template based on the column's data
void OnHeaderLoaded(object sender, RoutedEventArgs e)
{
GridViewColumnHeader header = (GridViewColumnHeader)sender;
GridViewColumn column = header.Column;
//select and apply your data template here.
e.Handled = true;
}
E) I guess you'd need also to acquire ownership of ItemsSource dependency property and handle it's changed event.
ListView.ItemsSourceProperty.AddOwner(typeof(MyListView), new PropertyMetadata(OnItemsSourceChanged));
static void OnItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyListView view = (MyListView)sender;
//do reflection to get column names and types
//and for each column, add it to your grid view:
GridViewColumn column = new GridViewColumn();
//set column properties here...
view.Columns.Add(column);
}
the GridViewColumn class itself doesn't have much properties, so you might want to add some information there using attached properties - i.e. like unique column tag - header most likely will be used for localization, and you will not relay on this one.
In general, this approach, even though quite complicated, will allow you to easily extend your list view functionality.
Have a DataTemplateselector to select one of the predefined templates(Of same DataType) and apply the selector on to the ListView. You can have as many DataTemplates with different columns.
You can use a DataTemplateSelector to return a DataTemplate that you have created dynamically in code. However, this is a bit tedious and more complicated than using a predefined one from XAML, but it is still possible.
Have a look at this example: http://dedjo.blogspot.com/2007/03/creating-datatemplates-from-code.html
From experience I can recommend steering clear of dynamic data templates if you can help it... rather use the advice given here to explictly create the ListView columns, rather than trying to create a DataTemplate dynamically.
Reason is that the FrameworkElementFactory (or whatever the class name is for producing DataTemplates at run time) is somewhat cludgey to use (and is deprecated in favor of using XAML for dynamic templates) - either way you take a performance hit.
This function will bind columns to a specified class and dynamically set header, binding, width, and string format.
private void AddListViewColumns<T>(GridView GvFOO)
{
foreach (System.Reflection.PropertyInfo property in typeof(T).GetProperties().Where(p => p.CanWrite)) //loop through the fields of the object
{
if (property.Name != "Id") //if you don't want to add the id in the list view
{
GridViewColumn gvc = new GridViewColumn(); //initialize the new column
gvc.DisplayMemberBinding = new Binding(property.Name); // bind the column to the field
if (property.PropertyType == typeof(DateTime)) { gvc.DisplayMemberBinding.StringFormat = "yyyy-MM-dd"; } //[optional] if you want to display dates only for DateTime data
gvc.Header = property.Name; //set header name like the field name
gvc.Width = (property.Name == "Description") ? 200 : 100; //set width dynamically
GvFOO.Columns.Add(gvc); //add new column to the Gridview
}
}
}
Let's say you have a GridView with Name="GvFoo" in your XAML, which you would like to bind to a class FOO.
then, you can call the function by passing your class "FOO and GridView "GvFoo" as arguments in your MainWindow.xaml.cs on Window loading
AddLvTodoColumns<FOO>(GvFoo);
your MainWindow.xaml file should include the following
<ListView x:Name="LvFOO">
<ListView.View>
<GridView x:Name="GvTodos"/>
</ListView.View>
</ListView>

Resources