Programmatically set content in Silverlight DataGrid details - silverlight

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

Related

WPF - Detect Explicit Tab Selection vs. programmatic Tab Selection

I have an application where I would like to explicitly set focus to particular content inside of TabItem Content, dependent on whether the user clicked the tab explicitly or whether the tab was activated via code by setting hte SelectedIndex. Specifically I don't want to set focus to the embedded document content when programmatically selected (as I can explicitly force it via code), but I do want to set it when activated via Tab header click.
I haven't been able to effectively intercept the tab header click operation. Tab and tab content container clicks don't seem to fire and inside of the SelectedIndex_Changed event there's no indication where the activation originated from.
Any ideas what I can look at to determine explicit manual vs. programmatic tab activation?
I had a solution for this. You can capture mouse click event on tab header and set a bool flag. Then check the same flag in 'SelectionChanged' event of tab control, you can do what you want here such setting focus, then reset the flag to identify further clicks. Here is the sample code.
<TabControl SelectionChanged="TabControl_SelectionChanged">
<TabItem>
<TabItem.Header>
<TextBlock Text="ABC" MouseDown="TextBlock_MouseDown"/>
</TabItem.Header>
<!--<TabItem.InputBindings>
<MouseBinding Gesture="LeftClick" />
</TabItem.InputBindings>-->
</TabItem>
<TabItem Header="XYZ" />
</TabControl>
In code behind you can check like this
private void TabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if(isClicked)
{
//you can set focus here
isClicked = false;
}
}
private void TextBlock_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
isClicked = true;
}
You can also try input bindings for tab item to detect the mouse click and raise the command, if you want to set the variable or take any required action. This way will be useful if you are following MVVM

How to run methods across pages in Silverlight?

I need to be able to set the visibility of the Border to be visible for 10 seconds. The border resides in MainPage.xaml which is parent to Content.xaml. The trick is that I need to change the visibility of the border by clicking ContextMenu item that is accessible from Content.xaml which is loaded as a UserControl into MainPage.xaml. It is also should be conditional bases on the cell value in the datagrid. I established a method in Content.xaml which should conditionally change visibility of the border in MainPage.xaml. Since the border is out of the scope, I need to find a way to be able wire to it.
Code to set the visibility based on the content in cell value in datagrid:
private void Delete(object sender, RoutedEventArgs e)
{
Packages_DataViewModel currentItem = MasterTile.SelectedItem as Packages_DataViewModel;
if (currentItem.Status != "has content")
{
this.MainPageBorder.Visibility = Visibility.Visible;
}
else
{
mv.DeletePackagesItem((Packages_DataViewModel)(MasterTile.SelectedItem));
}
}
I also need to run a method which I use in Content.xaml to modify data grid content from a button in MainPage.xaml. Any ideas are highly appreciated!
Code to update the cell value:
private void Status(object sender, RoutedEventArgs e)
{
Packages_DataViewModel currentItem = MasterTile.SelectedItem as Packages_DataViewModel;
currentItem.Status = "has content";
this.MainPageBorder.Visibility = Visibility.Collapsed;
}
The MainPage.xaml should always be your rootvisual. You can easily access the object via the
following code :
Application.Current.RootVisual
and this is accesible from everywhere in your silverlight application.
To answer your comment, the RootVisual IS your MainPage.xaml.
To access Methods in your Content.xaml, you need to set those methods to public. Then from the MainPage.xaml you can call it this way (by casting the content of the ucMainPage_MainContent to Page1 type).
((Page1)this.ucMainPage_MainContent.Content).TestMethod1();
(TestMethod1 is a new public method I added to Page1.xaml.)

WPF expand TreeView on single mouse click

I have a WPF TreeView with a HierarchicalDataTemplate.
Currently I have to double click an item to expand/collapse it.
I would like to change this behaviour to a single click, without loosing other functionality. So it should expand and collapse on click.
What is the recommended way to do this?
Thanks!
You could use a re-templated checkbox as your node (containing whatever template you are currently using) with its IsChecked property bound to the IsExpanded property of the TreeViewItem.
Here is a template I've just test that seems to do the job:
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsExpanded}">
<CheckBox.Template>
<ControlTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</ControlTemplate>
</CheckBox.Template>
</CheckBox>
</HierarchicalDataTemplate>
Just replace the ControlTemplate contents with whatever you need.
If you are using a standard TreeViewItem, then you can capture the click event:
private void OnTreeViewMouseUp( object sender, MouseButtonEventArgs e )
{
var tv = sender as TreeView;
var item = tv.SelectedItem as TreeViewItem;
if( item != null )
item.IsExpanded = !item.IsExpanded;
e.Handled = true;
}
private void OnTreeViewPreviewMouseDoubleClick( object sender, MouseButtonEventArgs e )
{
e.Handled = true;
}
Most likely in your case, you'll need to do something with your binding and ViewModel. Here's a good article from CodePlex: Simplifying the WPF TreeView by Using the ViewModel Pattern.
Just use selected item changed event and use the following,
private void treeview_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem item = (TreeViewItem)treeview.SelectedItem;
item.IsExpanded = true;
}
where treeview is the name of your TreeView, you could include an if to close/open based on its current state.
I have very little experience working with WPF to this point, so I am not 100% certain here. However, you might check out the .HitTest method of both the Treeview and TreeView Item (the WPF Treeview is essentially the Windows.Controls.Treeview, yes? Or a derivation thereof?).
THe HIt Test method does not always automatically appear in the Intellisense menu for a standard Windows.Forms.Treeview (I am using VS 2008) until you type most of the method name. But it should be there. You may have to experimnt.
You can use the .HitTest Method to handle the MouseDown event and return a reference to the selected treeview item. You must test for a null return, however, in case the use clicks in an area of the control which contains no Tree Items. Once you have a reference to a specific item, you should be able to set its .expanded property to the inverse of whatever it is currently. again, some experimentation may be necessary here.
As I said, I have not actually used WPF yet, so I could have this Wrong . . .
The answer of Metro Smurf (thanks to which I got where I wanted to be) suggests the right approach . You could simply hook up to the SelectedItemChanged event of the Treeview. Then cast the e.NewValue passed in the eventhandler as TreeViewItem, and access its IsExpanded property to set it to true.
void MyFavoritesTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
((TreeViewItem)e.NewValue).IsExpanded = true;
}
Then for the final touch, you can also hook up the items in your Treeview by casting them as TreeViewItem as suggested, and then you can hook up to the various manipulation events, like:
var item = tv.SelectedItem as TreeViewItem;
item.Expanded += item_Expanded;
And then do whatever you need to do in the eventhandler
void item_Expanded(object sender, RoutedEventArgs e)
{
// handle your stuff
}

WPF Datagrid autogenerated columns

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.

WPF Popup focus in data grid

I'm creating a custom UserControl to be used inside a DataGrid editing template.
It looks like this:
<UserControl
x:Class="HR.Controls.UserPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:tk="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock x:Name="PART_TextBox" Text="Hello WOrld" />
<Popup Width="234" Height="175" IsOpen="True" StaysOpen="True"
Placement="Bottom"
PlacementTarget="{Binding ElementName=PART_TextBox}"
>
<TextBox
x:Name="searchTextBox"
Text=">Enter Name<"/>
</Popup>
</Grid>
</UserControl>
edit:
I've narrowed down the code a bit.
It seems that if I put a Popup with textbox inside the CellEditingTemplate directly the textbox gets focus no problem. When I move that code into a UserControl I can no longer select the textbox when editing the cell.
Is the UserControl doing something funny with the focus ?
The problem is when i edit the cell in the datagrid I get the user control showing up but I can't click in the TextBox searchTextBox. When I click on it the popup closes and the cell goes back to default.
I have tried copying and pasting all the code inside the user control and pasting it directly into the CellEditingTemplate and that interacts the way it should.
I was just wondering if the UserControl did something weird that prevents a popup from gaining focus because it works as expected when directly placed in the CellEditingTemplate ?
Thanks,
Raul
Not sure if this will help anyone, but this helps if you have custom controls in the datagrid with a popup..... this fixed my problem, and it was one line of xaml. I spend the whole day re-reading this forum and then looking at the source for DataGridCell. Hope this helps.
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Focusable" Value="False"></Setter>
</Style>
I had a similar problem where a Popup embedded in a UserControl as a cell editing template would close when certain areas of it were clicked. The problem turned out to be that the WPF Toolkit (and presumably WPF4) DataGrid is very greedy with left mouse clicks. Even when you handle them and set Handled to true, the grid can interpret them as clicking into a different cell.
This thread has the full details, but the fix is to hook into DataGrid.CellEditEnding event and cancel the end edit:
private static void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (e.Column.GetType() == typeof(DataGridTemplateColumn))
{
var popup = GetVisualChild<Popup>(e.EditingElement);
if (popup != null && popup.IsOpen)
{
e.Cancel = true;
}
}
}
private static T GetVisualChild<T>(DependencyObject visual)
where T : DependencyObject
{
if (visual == null)
return null;
var count = VisualTreeHelper.GetChildrenCount(visual);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(visual, i);
var childOfTypeT = child as T ?? GetVisualChild<T>(child);
if (childOfTypeT != null)
return childOfTypeT;
}
return null;
}
Full credit for this goes to the Actipro thread.
Set FocusManager.IsFocusScope Attached Property on the Popup to True
I had a kinda simular problem, i created a usercontrol containing a textbox, a button and a calendar. Basicaly i create my own datepicker with custom validation logic.
I put this component in a CellEditingTemplate. When i pressed the button, the popup showed, but clicking the popup anywhere caused the cell te stop editing (because the popup was taking focus from the textbox). I solved it by adding code that sais that if the popup is open, the focus of the textbox may not be lost. This did the trick for me.
Also, the in the on loaded event handler of the usercontrol i give focus to the textbox.
In your case it's propably the Usercontrol itsefl that has focus.
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) {
// Don't allow focus to leave the textbox if the popup is open
if (Popup.IsOpen) e.Handled = true;
}
private void Root_Loaded(object sender, RoutedEventArgs e) {
TextBox.Focus();
}

Resources