Nested Silverlight Datagrid - Row Details works great, but I want a button! - silverlight-3.0

I'm using a silverlight 3 datagrid, and within it, I'm nesting related records in another control by using the rowdetails (visibilitymode = visiblewhenselected).
I really like how this works, but I'd much rather have the grid display the row details when a "+" button is pressed, much as a tree will expand when you click a node.
I tried programmatically defining the template by using resources like this:
<Grid.Resources>
<DataTemplate x:Key="EmptyTemplate">
<StackPanel>
<!--<TextBlock Text="Empty Template!!!" />-->
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="SongTemplate">
<StackPanel>
<AdminControls:ArtistSongControl x:Name="ArtistSongControl" />
</Stack>
</DataTemplate>
</Grid.Resources>
And in the grid's LoadingRowDetails event, I'd choose which template to set by:
e.Row.DetailsTemplate = (DataTemplate)LayoutRoot.Resources["SongTemplate"];
This sortof worked, but I found that I had problems with collapsing previous rows details template, and even crashed ie8 (not sure if that's related).
Basically, I really like how the silverlight 3 datagrid works, and even how the rowdetailstemplate stuff is implemented. I simply would like to defer loading any details until a row is expanded purposely (as a tree would be). All of the 3rd party grids seem to do this, and microsoft's is soooo close. Does anyone have any idea how to solve this one?
Thanks, Dennis

Dennis,
In case you haven't already found an answer to this, I wanted the same behavior and solved it by customizing the RowHeaderTemplate, which lets you throw a button in the header for each row. Then I implemented a handler for the button like so:
private void ToggleButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
ToggleButton button = sender as ToggleButton;
DataGridRow row = button.GetVisualAncestorOfType<DataGridRow>();
if (button.IsChecked == true)
{
row.DetailsVisibility = Visibility.Visible;
//Hide any already expanded row. We only want one expanded at a time for simplicity and
//because it masks a virtualization bug in the datagrid.
if (_expandedRow != null)
_expandedRow.DetailsVisibility = Visibility.Collapsed;
_expandedRow = row;
}
else
{
row.DetailsVisibility = Visibility.Collapsed;
_expandedRow = null;
}
}
Note that GetVisualAncestorOfType<> is an extension method I've implemented to dig into the visual tree.
You'll also need to set the datagrid's HeadersVisibility property to Row or All

here is another way to achieve what you are trying to do:
In the DataGrid set up a LoadingRow Event like this:
<data:DataGrid LoadingRow="ItemsGrid_LoadingRow" .....
In the DataGrid create a Template Column which will contain a Button such as the following:
<data:DataGridTemplateColumn CellStyle="{StaticResource DataGridCellStyle1}" CanUserReorder="False">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button x:Name="ViewButton" Click="ToggleRowDetailsVisibility" Cursor="Hand" Content="View Details" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
In the LoadingRow Event locate the button that is (in this case) stored in the first column of the DataGrid, then store the current DataGridRow into the buttons Tag element
private void ItemsGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
var ViewButton = (Button)ItemsGrid.Columns[0].GetCellContent(e.Row).FindName("ViewButton");
ViewButton.Tag = e.Row;
}
In the Buttons EventHandler (in this case ToggleRowDetailsVisibility) we will extract the Row so that we can toggle its DetailsVisibility
In the LoadingRow Event locate the button that is (in this case) stored in the first column of the DataGrid, then store the current DataGridRow into the buttons Tag element
private void ToggleRowDetailsVisibility(object sender, RoutedEventArgs e)
{
var Button = sender as Button;
var Row = Button.Tag as DataGridRow;
if(Row != null)
{
if(Row.DetailsVisibility == Visibility.Collapsed)
{
Row.DetailsVisibility = Visibility.Visible;
//Hide any already expanded row. We only want one expanded at a time for simplicity and
//because it masks a virtualization bug in the datagrid.
if (CurrentlyExpandedRow != null)
{
CurrentlyExpandedRow.DetailsVisibility = Visibility.Collapsed;
}
CurrentlyExpandedRow = Row;
}
else
{
Row.DetailsVisibility = Visibility.Collapsed;
CurrentlyExpandedRow = null;
}
}
}
You will notice that "CurrentlyExpandedRow", this is a Global variable, of type DataGridRow, that we store the currently expanded row in, this allows us to close that Row when a new one is to be opened.
Hope this helps.

In addition to the answer uxrx provided here is the code for finding an ancestor
public static partial class Extensions
{
public static T FindAncestor<T>(DependencyObject obj) where T : DependencyObject
{
while (obj != null)
{
T o = obj as T;
if (o != null)
return o;
obj = VisualTreeHelper.GetParent(obj);
}
return null;
}
public static T FindAncestor<T>(this UIElement obj) where T : UIElement
{
return FindAncestor<T>((DependencyObject)obj);
}
}

For this:
DataGridRow row = button.GetVisualAncestorOfType<DataGridRow>();
We can use as:
HyperlinkButton button = sender as HyperlinkButton;
DataGridRow Row = DataGridRow.GetRowContainingElement(button)

I suggest you take a look at the RowVisibilityChanged event on the datagrid, basically when the row visibility changes to "Visible", then load the info for the row.

Related

ICommand not always firing when tab selected

I have a simple ActionBar with 3 tabs attached. When a tab is clicked, the fragment is inflated and the view shows. The tab being click event is fired using an event. Initially, the first fragment is inflated, but the others respond and inflate if clicked.
If I change the event being fired to an ICommand, only the last fragment is inflated and then if I click on the first tab, that and the last are inflated. Never the second.
My code is this
ICommand TabClicked
{
get
{
return new RelayCommand(() =>
{
tab.TabSelected += (object sender, ActionBar.TabEventArgs e) => TabOnTabSelected(sender, e);
});
}
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
fragments.Add(new TODFragment());
fragments.Add(new ConditionsFragment());
fragments.Add(new ResultsFragment());
AddTabToActionBar("Time", Resource.Drawable.crucifix_colour);
AddTabToActionBar("Conditions", Resource.Drawable.weather_colour);
AddTabToActionBar("Results", Resource.Drawable.tod_colour);
}
void AddTabToActionBar(string text, int iconResourceId)
{
tab = ActionBar.NewTab().SetTag(text).SetText(text).SetIcon(iconResourceId);
/* uncomment and comment out one of the two below to see the difference in operation */
tab.TabSelected += TabOnTabSelected;
//tab.SetCommand<ActionBar.TabEventArgs>("TabSelected", TabClicked);
ActionBar.AddTab(tab);
}
void TabOnTabSelected(object sender, ActionBar.TabEventArgs tabEventArgs)
{
var tabNo = sender as ActionBar.Tab;
var frag = fragments[tabNo.Position];
tabEventArgs.FragmentTransaction.Replace(Resource.Id.frameLayout1, frag);
}
Am I missing something fundamental here in the difference between ICommands and Events or is it something else?
I'm using Xam.Android and MVVMLight
I found the answer. When I create the partial class I define the UI objects like this (or something like this at least)
EditText myEditText;
EditText MyEditText = myEditText ?? (view.FindViewById<EditText>(Resources.Id.myEdit);
This is fine, but it does mean that once defined, it doesn't get redefined.
Not a problem if the UI is not really going to change, but every time an action tab is pressed, the fragment is refreshed. Only problem is the Id isn't changing as myEditText is not null.
The answer is add a method in the UI definition code that nulls the objects then in the main code, when the UI disappears, call the nulling method. Everything works then

ListGrid put focus in the FilterEditor

I have a ListGrid defined like this:
ListGrid lgrid = new ListGrid();
ListGridField first = new ListGridField("first",first");
ListGridField second = new ListGridField("second ",second ");
lgrid.setFields(first, second);
lgrid.setShowFilterEditor(true);
¿How can i put the keyboard focus in the first filter editor field after i call show() in the layout?
Thxs in advance.
Depending on what your use case is (which would be useful to provide a more focused answer), the solution you posted might not be what you really need, because if you scroll on your ListGrid, it could trigger a new data fetch (if there are more records to show), and move the cursor to the filter editor as a result (if your user is editing some records at that point, the cursor moving to the filter row is not what she would want to happen!!).
In such a case, you probably just want to call grid.focusInFilterEditor("fieldToFocus") after the listGrid.show() statement or in the ClickHandler of some button you use to fetch the data, etc.
Anyway, you don't need the Timer either. This works:
listGrid.addDataArrivedHandler(new DataArrivedHandler() {
#Override
public void onDataArrived(DataArrivedEvent event) {
grid.focusInFilterEditor("fieldToFocus");
}
});
I got the solution, its focusInFilterEditor, this is an example to set the focus after the data arrived to the grid:
// Put the focus on the first listGrid field when is loaded
listGrid.addDataArrivedHandler(new DataArrivedHandler() {
#Override
public void onDataArrived(DataArrivedEvent event) {
Timer t = new Timer() {
public void run() {
if(listGrid.getFilterEditorCriteria() == null){
listGrid.focusInFilterEditor("fieldToFocus");
}
}
};
t.schedule(600);
}
});

caret position in an editable div (dartlang)

I try to find the caret position in an editable div.
Also it should be nice to get the selected text in this div.
I try to assimilate this:
Editable Div Caret Position
But it dosen’t work.
I'll be happy to give any Idea.
Thanks in advance
Some code snippet
HTML
<a on-click="{{chooseMenu}}" which-menu="1">Menu 1</a>
Dart
void chooseMenu(Event event, var detail, var target) {
event.preventDefault();
event.stopPropagation();
Selection sel;
Range range;
sel = window.getSelection();
if(sel != null) {
range = sel.getRangeAt(0);
range.collapse(false);
}
currentSubMenu = int.parse(target.attributes['which-menu']);
}
This worked for me in Dartium.
The other code from the referenced SO question is probably to support other browsers.
Dart usually generates JS code that works in all supported browsers.
You have to test yourself if the generated JS really works in the browsers you want to support.
EDIT
We store the selection whenever it changes. Maybe there are better events but I couldn't find one.
But it worked fine for me.
We insert the new node at the previously stored selection.
// must be accessible by getRange, insertNodeAfterSelection
DivElement editable;
Range range;
// store reference to our contenteditable element
editable = (document.querySelector('#editable') as DivElement);
// subscribe selection change events
document.onSelectionChange.listen(getRange);
void insertNodeAfterSelection(Node node) {
range.collapse(false);
range.insertNode(node);
}
void getRange(Event e) {
if(document.activeElement != editable) { // save only selection changes on our contenteditable
return;
}
print(e);
Selection sel;
sel = window.getSelection();
if(sel != null) {
range = sel.getRangeAt(0);
}
}
You have to decide yourself where you put those code parts. I added some comments as support.
EDIT
using the mousedown event instead of click should help around this issue
Preserve text selection in contenteditable while interacting with jQuery UI Dialog and text input

Monotouch.Dialog - How to push a view from an Element

It seems like this should be very easy, but I'm missing something. I have a custom Element:
public class PostSummaryElement:StyledMultilineElement,IElementSizing
When the element's accessory is clicked on, I want to push a view onto the stack. I.e. something like this:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (MyParent != null) {
MyParent.PresentViewController(new MyDemoController("Details"),false,null);
}
};
Where MyDemoController's gui is created with monotouch.dialog.
I'm just trying to break up the gui into Views and Controlls, where a control can push a view onto the stack, wiat for something to happen, and then the user navigates back to the previous view wich contains the control.
Any thought?
Thanks.
I'd recommend you not to hardcode behavior in AccessoryTapped method, because the day when you'll want to use that component in another place of your project is very close. And probably in nearest future you'll need some another behavior or for example it will be another project without MyDemoController at all.
So I propose you to create the following property:
public Action accessoryTapped;
in your element and its view, and then modify your AccessoryTapped is that way:
this.AccessoryTapped += () => {
Console.WriteLine ("Tapped");
if (accessoryTapped != null) {
accessoryTapped();
}
};
So you'll need to create PostSummaryElement objects in following way:
var myElement = new PostSummaryElement() {
accessoryTapped = someFunction,
}
...
void someFunction()
{
NavigationController.PushViewController (new MyDemoController("Details"), true);
}

Silverlight - ListBox with a button dynamically bound - how do I get the original list item data?

I've got a ListBox, within it I have a custom DataTemplate that builds a button. Each button represents a selection the user can perform.
When the button is clicked, is there anyway I can retrieve the original data record for that bound item? In standard C#, I can create a button and use CommandArgument to pass an ID to an event. Is there something similar in Silverlight?
Thanks
private void RemoveMember_Click(object sender, RoutedEventArgs e)
{
var employee = ((Button)sender).DataContext as Employee;
if(employee == null)
return;
_employeeList.Items.Remove(employee);
}

Resources