Weird gridview behaviour - c#

I try to be more clear in titles... but... this is just weird...
1st: The setup
Working with Xamarin. I have 2 grid views. The 1st one is created and populated when the application opens. The 2nd one is created and populated when an item in the 1st grid view is clicked
Both gridviews are created with the same contructor and the same adapter classes.
2nd: The issue
The 1st thing I noticed was that when the second gridview was created, the 1st item of the 1st gridview would dissapear. Regardless of where I would click. With both grids visible the app looks like this:
Its easy to make it reappear, I just need to add a adapter.NotifyDatasetChanged() to the behaviour that makes the 1st cell dissapear and the cell never dissapers. Great... altough its a patch, not a solution, and this patch is not good enough when there's a lot of grid creation going on. Moreover, I also noticed that if I scroll down and back up, the cell would also reappear (likely because the view is forced to refresh the initial views when they come back to visible range). I figured my issue was the implementation of my custom adapter. Thus, I spent a decent amount of time looking at my GetView implementation. Then I noticed something weirder: If I scroll down and then back up the views refresh, just as I said before, however, if I only scroll a little, like halfway down the cell that is no longer visible, and then back up, this happens:
Yeah... it kinda keeps scrolling up forever. Is the problem still my adapter implementation?
My GetView is pretty simple, I have a separated list with views and the GetView fetchs the required view there:
public override View GetView (int position, View convertView, ViewGroup parent)
{
RelativeLayout temp;
if (convertView != null) {
temp = (RelativeLayout)convertView;
} else {
temp = new CustomGestureListener (Android.App.Application.Context);
}
temp.RemoveAllViews ();
RelativeLayout.LayoutParams cellLP = null;
if (data [position] != null) {
RelativeLayout referenceView = (RelativeLayout)data [position].NativeObj;
if(referenceView != null && referenceView.Handle != IntPtr.Zero){
View refParent = (View)referenceView.Parent;
if (refParent != null)
((ViewGroup)refParent).RemoveView (referenceView);
cellLP = new RelativeLayout.LayoutParams (RelativeLayout.LayoutParams.MatchParent, referenceView.LayoutParameters.Height);//(RelativeLayout.LayoutParams)referenceView.LayoutParameters;
temp.AddView (referenceView, cellLP);
}
}
return (View)temp;
}
I have been using the same adapter in my listviews without issues. Even if I replace the 1st grid by a Listview with this same adapter and I recreate this behaviour of creating a gridview, the issue does not happen in the ListView

Related

Button click binds to multiple views in custom list view

I'm using a custom list adapter for a list view. I have defined a button in the list view and the click event works, but the problem is that once the list is scrolled, it binds multiple views with the same button. So on the click of the button, the event associated with each of the associated views is fired.
How do I deal with this?
I would guess that you are misunderstanding how the list works - especially how convertView's are used.
ListViews in Android virtualise the UI - just like ListBoxes do in WP and just like UITableViews do in iOS
What this means is that if the underlying list has 1000 items, but the screen only has room for 10 items, then the list will just create 10 'containers' to show list items, and will use those containers to display just the content that is in view at the time.
The way it does this is through the Adapter - and in particular through the GetView callback - which takes a convertView as one of its parameters.
If you choose to create a new view in your GetView implementation, then you can subscribe to new events in the callback...
If instead you choose to use the convertView in your GetView implementation, then you should not subscribe to new events in the callback - not without unsubscribing the old events first.
e.g. I'm guessing your code does something like this pseudo currently:
public View GetView(int pos, View convertView)
{
TextView toShow = convertView as TextView;
if (toShow == null)
{
toShow = new TextView();
}
toShow.Text = "Item at position " + i;
toShow.Click += (s,e) => {
// do something
};
return toShow;
}
The problem with the code is that you will subscribe to Click too often... you'd need to solve it with something like:
public View GetView(int pos, View convertView)
{
TextView toShow = convertView as TextView;
if (toShow == null)
{
toShow = new TextView();
toShow.Click += (s,e) => {
// do something with the position embedded in toShow.Tag
};
}
toShow.Text = "Item at position " + i;
toShow.Tag = new WrappedPosition(i);
return toShow;
}
That's my guess anyways :)
Stuart is completely right - problem is that views in ListView are reused (to avoid creating different objects), and as different parts of list are visible, for a new position you could get any view that is not used anymore. So your code should handle this properly.
I would like to add that Garbage Collection for Java objects in monodroid works not good. In my experience, creating lots of objects derived from Java.Lang.Object will crash the application. So:
Creating new View for each new row will soon crash the applicaion, so you have to reuse convertView whenever possible.
Tag has type Java.Lang.Object, so WrappedPosition should derive from Java object. This means that rather than creating new instance every time, you should reuse same instance.
If you move click handler to a separate method, you can just unsubscribe before subscribing, so you will not need any logic "if view is null".
If you find it useful, I may post here example of code that explains how it works. Dodn't post it initially as it's quite big :)

Two-Way Binding is not working

In my Silverlight project I am creating textboxes which are two-way databound to some Context during runtime. The binding in one direction (from the source to the target) seems to work fine, but the other direction (from the target back to the source) is not showing any effect.
This is the data-context:
public class Leg : INotifyPropertyChanged {
private string passengers;
public string Passengers {
get { return passengers; }
set {
// here I have a breakpoint.
passengers = value;
FirePropertyChanged("Passengers");
}
}
private void FirePropertyChanged (string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then on another place I am creating a new TextBox control together with a binding for it:
Binding passengersBinding = new Binding();
// viewModelLeg is an instance of the class Leg from above
passengersBinding.Source = viewModelLeg;
passengersBinding.Path = new PropertyPath("Passengers");
passengersBinding.Mode = BindingMode.TwoWay;
legItem.paxTextBox.SetBinding(TextBox.TextProperty, passengersBinding);
Now when I am altering the value of the Passengers string the corresponding textbox that is bound to it is updating its text correctly. So here's everthing fine.
But when i change the text of a textbox manually and then make the textbox lose its focus, nothing happens - i.e. there is no two-way binding taking place - no down propagation of the new text-value of the textbox to the source !
I have a breakpoint at the setter of the passengers-attribute (marked with the breakpoint-comment above). When I am getting all this right the binding engine also uses this public setter when the target-value of a binding has changed to update the source - so when this happens the breakpoint must be hit. But he doesn't ! So it seems that i can do what I want with my textbox (play with the focus or press enter) it is never updating its source.
Am I overseeing something ? There must be a capital error either in my code or in my thinking.. i would be really thankful for any ideas ...
EDIT:
In the following I try to demonstrate how i create my XAML objects and my DataContext objects. Because I am creating XAML controls and their bindings at runtime I haven't found a good solution to implement the MVVM approach very well. So I am doing the following (which is maybe not the best way to do it):
The situation I am modelling is that I have a UserControl (called LegItem) which is comprised (primarely) of textboxes. At runtime the user can create as much of these userControls as hew wishes to (one after the other).
On my ViewModel side I have a class (called Leg) that serves as a ViewModel for exactly one LegItem. So when I have say n (XAML-) LegItems then I also have n Leg instances. I store these Leg objects in a List.
So I am doing the following everytime the user clicks the 'add a new leg' button:
// here we are inside the applications view in an .xaml.cs file
public void AddLeg () {
// this is going to serve as the ViewModel for the new LegItem
// I am about to create.
Leg leg = viewModel.insertLeg();
// here I am starting to create the visual LegItem. The ViewModel object
// I have created in the previous step is getting along with.
createLegItem(leg);
}
// the primary job here is to bind each contained textbox to its DataContext.
private LegItem createLeg (Leg viewModelLeg) {
// create the visual leg item control element
// which is defined as a XAML UserControl.
LegItem legItem = new LegItem();
Binding passengersBinding = new Binding();
// viewModelLeg is an instance of the class Leg from above
passengersBinding.Source = viewModelLeg;
passengersBinding.Path = new PropertyPath("Passengers");
passengersBinding.Mode = BindingMode.TwoWay;
legItem.paxTextBox.SetBinding(TextBox.TextProperty, passengersBinding);
}
// on the viewModel side there is this simple method that creates one Leg object
// for each LegItem the View is creating and stores inside a simple list.
public Leg InsertLeg () {
Leg leg = new Leg();
legList.add(leg)
return leg;
}
New Answer
Since you mentioned your binding was actually to a custom UserControl and not actually a TextBox, I would suggest looking into the XAML of your UserControl and making sure it is binding the data correctly
Old Answer
I did a quick test with a new Silverlight project and noticed that the startup project is SilverlightApplication1.Web, not SilverlightApplication.
This means that the breakpoint in the setter won't actually get hit when I run the project. You'll notice the breakpoint circle is just the outline, and the color isn't filled in. If you hover over it, it will say
The breakpoint will not currently be hit. No symbols have been loaded
for this document
If I start SilverlightApplication1 instead of the .Web version, the breakpoint gets hit.
The property is getting changed correctly regardless of which version I startup, however the breakpoint isn't getting hit if I start the project with the .Web version. I suspect this is your issue.

Bring button to front on Windows Phone UI

On the main page of my app I have a grid with 6 x 4 columns and rows of buttons.
I want to move one of these buttons to the middle and then scale it larger using RadControls by Telerik.
I can do this easily however when I do the button is shown behind all the other buttons on the grid and I can't seem to make it come to the front.
Any help would be much appreciated.
The order that items were added to a panel/grid determines what control is above another. Looking around it looks like you have two options:
Change the Canvas.ZIndex for the button you want to be on top.
Yes it seems odd as there is no canvas, but it works for any panel or grid.
Remove and re-add the child from the parent grid so that it was last. I found a nice snippet of code here posted by "CleverCoder" : http://forums.silverlight.net/post/63607.aspx
//Originally posted by CleverCode - http://forums.silverlight.net/post/63607.aspx
public static void PushToTop(this FrameworkElement element)
{
if (element == null) throw new ArgumentNullException("element");
var parentPanel = element.Parent as Panel;
if (parentPanel != null)
{
// relocate the framework element to be the last in the list (which makes it "above" everything else)
parentPanel.Children.Remove(element);
parentPanel.Children.Add(element);
parentPanel.UpdateLayout();
}
}

Stopping line jump when refreshing a C# datagrid

We display our data on datagrids, bound to a dataset, which is in turn fed from a Progress database on the server. During processing, we need to make a change to the data-set and refresh it's value from the server. So far, all well and good and no problems.
The problem is that when we come back with the new data, we want the selection in the datagrid to remain on the same row it was on before. We've managed this with the following code:
int iPostingPos = dgridPostings.CurrentRow.Index;
// process data on server
dataContTranMatch.RunBoProcedure(dataContTranMatch.BoProcedure,
transactionMatchingDataSet);
// Reload Data
LoadData();
if (iPostingPos > ttPOSTingsRowBindingSource.Count)
{
iPostingPos = ttPOSTingsRowBindingSource.Count;
}
if (ttPOSTingsRowBindingSource.Count > 0)
{
ttPOSTingsRowBindingSource.Position = iPostingPos;
dgridPostings.Rows[iPostingPos].Selected = true;
}
This works, but we get the selected line jumping about on the screen, which is really annoying the users.
For example, if you select row 7, then run this code, you have row 7 selected, selection then jumps to row 0, then jumps back to row 7. This isn't acceptable.
In an attempt to fix this, we've tried enclosing the above code in the following additional lines:
chTableLayoutPanel1.SuspendLayout();
*DO CODE*
chTableLayoutPanel1.ResumeLayout();
But this didn't help.
So far, the most acceptable solution that we've been able to reach is to change the colour on the selection so that you can't see it, letting it leap about and then putting the colours back as they should be. This makes the flicker more acceptable.
dgridPostings.RowsDefaultCellStyle.SelectionBackColor =
SystemColors.Window;
dgridPostings.RowsDefaultCellStyle.SelectionForeColor =
SystemColors.ControlText;
DO CODE
dgridPostings.RowsDefaultCellStyle.SelectionBackColor =
SystemColors.Highlight;
dgridPostings.RowsDefaultCellStyle.SelectionForeColor =
SystemColors.HighlightText;
We beleive that the issue is caused by the binding source being temporarily empty as the dataset is refreshed, we then re-navigate one it's got data in it again.
Can anyone offer any ideas on how we can prevent this unpleasent flicker from occuring?
Many thanks
Colin
It may be a bit heavy handed but one option would be to suspend painting of the control. A user asked how to achieve this here: How Do I Suspend Painting For a Control and Its' Children. I've used the selected answer there to achieve something similar.

Set selected WPF DataGridCell to focused and editable upon event

Woot, first Stack Overflow post! I've been asked to work on a desktop application to improve an inventory process for my company. I dabbled with WPF in school and I figured I'd start there. After researching some, I learned about MVVM, put a design together, and forged ahead. Finally, I'm stuck and looking for some help and also a sanity check to see if I'm on the right path.
I have single-column DataGrid bound to an observable collection. Users of the application use a scan gun to enter values in. One potential value that I catch in my "Cell" model object is a "MoveNextColumn" value. This raises a custom event in my model that is handled in the View Model. The handler is supposed to simulate blank entries for all remaining rows in that column, set focus on the last row, and wait for input before moving on. So here is what I have so far:
private void dummyCell_MoveToNextColumn(object sender, RoutedEventArgs e) {
e.Handled = true;
// Cell is the model object containing the parsing rules and raising events
var lSender = sender as Cell;
var gridItems = ViewGridReference.Items;
var lastItem = gridItems[gridItems.Count - 1];
if (lSender == lastItem) {
// We are at the bottom of the column
// Move the program on to the next column
CurrentColumn++;
OnPropertyChanged("ItemPositions");
} else {
// Simulate "empty position" input for this cell and all cells down the column
// Cells are validating themselves as the simulation progresses
foreach (Cell item in ViewGridReference.Items) {
item.ActualItemCode = string.Empty;
}
// ViewGridReference is a reference to my DataGrid set from the view
ViewGridReference.Focus();
ViewGridReference.SelectedIndex = gridItems.Count - 1;
ViewGridReference.CurrentCell = new DataGridCellInfo(lastItem, ViewGridReference.Columns[0]);
((DataGridCell)ViewGridReference.SelectedItem).Focus();
}
}
All of this seems to be working as expected: all rows receive blank input and are validated (I use color properties in the cell to which the view binds to signify the validity of the entry).
Unfortunately, though the focus is on the last row as desired, it is not editable and the user cannot submit another "MoveNextColumn" value which would move the program on. The goal here is to minimize any keyboard interaction. Everything should be done with scan guns and barcodes.
Any ideas on how to make the selected cell editable after this code executes?
Any "hey, your design sucks" feedback would be cool too. This is new to me and I'm open to constructive criticism.
I have made some progress with this. The entire grid was left at an uneditable state in the code above. This now leaves focus on the last cell in my column and allows me to submit input with the scan gun.
This seems to work, but I'd still appreciate some feedback on whether there is a better way.
private void dummyCell_MoveToNextColumn(object sender, RoutedEventArgs e) {
e.Handled = true;
// Cell is the model object containing the parsing rules and raising events
var lSender = sender as Cell;
var gridItems = ViewGridReference.Items;
var lastItem = gridItems[gridItems.Count - 1];
if (lSender == lastItem) {
// We are at the bottom of the column
// Move the program on to the next column
CurrentColumn++;
OnPropertyChanged("ItemPositions");
} else {
// Simulate "empty position" input for this cell and all cells down the column
// Cells are validating themselves as the simulation progresses
foreach (Cell item in ViewGridReference.Items) {
item.ActualItemCode = string.Empty;
}
ViewGridReference.SelectedIndex = gridItems.Count - 1;
ViewGridReference.CurrentCell = new DataGridCellInfo(lastItem, ViewGridReference.Columns[0]);
(ViewGridReference.ItemsSource as ListCollectionView).EditItem(ViewGridReference.SelectedItem);
((DataGridCell)ViewGridReference.SelectedItem).Focus();
}
}
Updated 12/2/2010
Hey, there is an important update to this. The first thing to note is that text entry is being done with a scan gun in my scenario, so 'Enter' keys are sent down with each pull of the trigger. It shoots down each character followed by the Enter key all at once.
WPF sees this enter and wants to set the focus to the DataGridCell directly beneath the cell in which the Enter key input was received. The code above sets the focus to the last cell, but then the Enter key event still fires and is handled by DataGrid after this code is run. The effect is that the focus is reset back to the subsequent cell, not the last cell like I want.
So I need to either figure out how to eat the Enter key for just that scan, or I need to break how WPF handles Enter keys. The last line up there actually throws an exception. We are trying to use a Model class (Class.cs) as a DataGridCell, and there is nothing to handle that cast. Because of that, the Focus() method tries to operate on a null object and we get a NullReferenceException. This was really confusing me because Visual Studio 2010 would sometimes break to alert me about this, but sometimes it wouldn't. However, if I run the executable outside of Visual Studio, it works just fine. That's because unhandled, non-fatal exceptions are ignored and the Enter key behavior fails to operate as normal.
So it works, but in a pretty gross way. I either need to figure out how to do one-time handling of the Enter key and override the default WPF handler, or just leave it like it is and grimace.

Categories