GridView doesn't scroll down to virtualized items - c#

In my application, I need to select the newly created document(note) when I go back to library. After library item is selected, the Library must be scrolled to the selected item.
My library's OnLoaded method:
private async void OnLoaded(object sender, RoutedEventArgs e)
{
await this.ViewModel.InitializeAsync();
// CollectionViewSource of my GridView being filled
ViewModel.CollectionChanging = true;
GroupInfoCVS.Source = ViewModel.GroupsCollection;
ViewModel.CollectionChanging = false;
// Loading Last selected item - THIS CHANGES SELECTION
ViewModel.LoadLastSelection();
}
After I call the LoadLastSelection method, selection is changed successfuly (I've tested). This is the method that is called after that (in our GridView's extended control):
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsCount = this.SelectedItems.Count;
var newlySelectedItems = e.AddedItems;
if (newlySelectedItems.Any())
{
var item = newlySelectedItems.Last();
ScrollTo(item);
}
}
private void ScrollTo(object item)
{
UpdateLayout();
var itemUI = (FrameworkElement)this.ContainerFromItem(item);
if (itemUI != null)
{
_scrollViewer.UpdateLayout();
_scrollViewer.ChangeView(null, itemUI.ActualOffset.Y - itemUI.ActualHeight, null, false);
}
}
This also works for the most part. When itemUI is not null, the method scrolls successfully to the required item. The problems start when the items start to overflow the screen size. When items are completely hidden from the screen, they are virtualized. That means that ContainerFromItem returns null, so I can't take the offset properties. Keep in mind that this actually occurs before Library's OnLoaded method is finished.
Please, help me with some alternative to get such properties or other methods of scrolling, which will help me scroll successfully.
I've read a lot and tried using Dispatcher.RunAsync and ScrollIntoView methods, but I couldn't manage to produce any scrolling behavior. If you point me how to use them successfully, that would be a nice help too.
Here's what I've read (and tried):
ItemContainerGenerator.ContainerFromItem() returns null?
How to Know When a FrameworkElement Has Been Totally Rendered?
Is there a "All Children loaded" event in WPF
Let ListView scroll to selected item
Thanks in advance!
IMPORTANT: If you don't want to read all the conversation within the official answer, please read the solution in short here:
TemplatedControl's style had changed ScrollViewer's name from "ScrollViewer" to "LibraryScrollViewer" and that rendered ScrollIntoView method useless.

For GridView, the best way to achieve your needs is to call GridView.ScrollIntoView.
But you seem to have made similar attempts, and it does not to be successful, then the following points may help you:
1. Don't use GridView as a child element of ScrollViewer.
In your code, I see that you are calling the method of ScrollViewer.ChangeView to adjust the view scrolling, so it is speculated that you may put the GridView in the ScrollViewer, which is not recommended.
Because there is a ScrollViewer inside the GridView, and its ScrollIntoView method is to change the scroll area of the internal ScrollViewer. When there is a ScrollViewer outside, the ScrollViewer inside the GridView will lose the scrolling ability, thus making the ScrollIntoView method invalid.
2. Implement the Equals method of the data class.
If your data class is not a simple type (such as String, Int32, etc.), then implementing the Equals method of the data class will help the GridView to find the corresponding item.
Thanks.

Related

UWP CustomRenderer for Checkbox: Pointer over Checkbox changes style?

I'm working with Xamarin.Forms and I made a CustomRenderer for Checkbox in UWP. When I set all the Checkboxes of my items in the ListView to true by clicking the button "Alle", the Checkboxes are displayed correctly with the check inside the box:
However, if I hover my mouse over the Checkboxes, they immediately change their appearence (the check disappears but it's still selected). In the following screenshot, I moved my cursor over the 3rd - 7th Checkboxes:
This is my overridden OnElementChanged method in the CustomRenderer:
protected override void OnElementChanged(ElementChangedEventArgs<EvaCheckbox> e)
{
base.OnElementChanged(e);
var model = e.NewElement;
if (model == null)
{
return;
}
nativeCheckbox = new CheckBox();
CheckboxPropertyChanged(model, null);
model.PropertyChanged += OnElementPropertyChanged;
nativeCheckbox.Checked += (object sender, Windows.UI.Xaml.RoutedEventArgs eargs) =>
{
model.IsChecked = (bool)nativeCheckbox.IsChecked;
};
nativeCheckbox.Unchecked += (object sender, Windows.UI.Xaml.RoutedEventArgs eargs) =>
{
model.IsChecked = (bool)nativeCheckbox.IsChecked;
};
SetNativeControl(nativeCheckbox);
}
I tried to override the PointerEntered event of nativeCheckbox. It works, for example if I set the model.IsChecked to true on this event, it will be set to true:
nativeCheckbox.PointerEntered += (s, args) =>
{
model.IsChecked = true;
};
But I don't know how to (if even at this place) prevent the checkbox from changing it's appearance when moving the cursor above the Checkbox. Just leaving the triggered event with empty code like this won't change anything about the described behaviour:
nativeCheckbox.PointerEntered += (s, args) => { };
How can I prevent the Checkbox from changing it's appearance when I move my cursor over it?
Update:
I've created a sample project for this issue. You can find the repository here: https://github.com/Zure1/CustomCheckbox
It has the exact same described behavior. In the following screenshot I pressed the button "All" on the bottom of the screen and then the checkboxes look like correct with a check inside of them:
After moving the mouse cursor over the bottom 3 checkboxes, their change their appearance:
Information: I'm debugging on my desktop (Windows 10). I don't know if this issue exists on WinPhone. Just in case you're wondering why my checkboxes are red: My system color in Windows is red.
This is a tricky one as I have been struggling with this issue for a while, I'll try my best to answer this.
TL;DR: It's caused by ViewCell.
The issue comes down to Xamarin Forms ListView and ViewCell.
I haven't been able to track down the cause yet for many months and the way I get around this issue is by refreshing the ListView every time a change happens forcing a redraw of the entire ListView which can really impact performance.
My educated guess on what the cause could be is the rendering code for the ViewCell is missing something.
As for your particular issue, I have created a CheckBoxCell which you can use to display a list of checkboxes with a title. I forked your project and made the changes.
This will display something similar to what you are trying to achieve and doesn't have rendering issues so will be a good starting point. You are able to customize this to display images and the like but you'll have to do that in the platform-specific layout code.
Please note that I have only created the code for UWP and that should be enough to get you going for the other platforms.
I hope this helps somewhat.

Does Grid.Children.Clear() really remove all controls?

Edit:
Simple answer: Yes, it does. I found the error, which was, that another event handler was added, everytime the Combobox_SelectionChanged was fired. Hence, the collection looked fine, but the Items_CollectionChanged was fired multiple times. Once this was fixed, everything worked fine.
End Edit.
I've got a page with a combobox and a grid. The grid fills dynamically, when the selection in the combobox changes. I'm now observing a strange thing. When I select a value for the second time in the combobox, the childitems in the grid appear twice. I've checked the underlying collections, which look fine (i.e. only one record per item). When I jump out of the combobox_SelectionChanged method, after the Grid.Children.Clear() command, the screen looks fine, i.e. empty.
My guess is, that Grid.Children.Clear() only removes the controls from the visual tree, but the actual controls are still hanging around. Any Ideas?
private void combobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
grItems.Children.Clear();
grItemsColumnDefinitions.Clear();
grItemsColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(200) });
}
private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
grItems.Children.Add(new ChildItemControl(e.NewItems[0]));
}
}
Edit: The whole thing is supposed to look like this (fictional - but hopefully understandable - example)
I would suggest you use the built in Databinding for WPF. You could use something like this:
<DataGrid x:Name="grItems" ItemsSource="{Binding comboboxItems}" />
Then, when you update comboboxItems your grid will automatically update too!
Here is a great article on Databinding with the DataGrid control:
http://www.wpftutorial.net/DataGrid.html
For more information about Databinding in general, here is a good article: https://msdn.microsoft.com/en-us/library/aa480224.aspx

Why does Listbox.Drawitem get called many times?

I have a databound listbox on my C# WinForm that holds strings that are links to image file locations. I want to display the image as a thumbnail for the user to click on and view. I got it working correctly by setting DrawMode=OwnerDrawVariable and handling the DrawItem and MeasureItem events.
However I noticed that I have to click exit 2 times to get out of the application (looks like it's called selectedIndexChanged on first click, then exits on second). On further inspection I noticed that the DrawItem event is fired a multitude of times when I click an item in the listbox (like 15+ times). There is only 1-2 items EVER in the listbox at a time! Why is it getting called soo many times?
I tested this with a non databound simple listbox and it does the same thing. I am only curious as I have to read the image from disk and get a thumbnail of it to put into the listbox, which if it did that 15-20 times could affect performance (and is TOTALLY unnecessary).
private void listBox1_MeasureItem(object sender, MeasureItemEventArgs e)
{
MessageBox.Show("listBox1_MeasureItem");
// Cast the sender object back to ListBox type.
ListBox theListBox = (ListBox)sender;
// Get the file path contained in each item.
DataRowView drv = (DataRowView)theListBox.Items[e.Index];
string fileString = drv.Row["fullpath"].ToString();
// Create an image object and load image file into it
Image img = Image.FromFile(fileString);
e.ItemHeight = Convert.ToInt32(img.Height * 0.15);
e.ItemWidth = Convert.ToInt32(img.Width * 0.15);
}
private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
{
MessageBox.Show("listBox1_DrawItem");
// If the item is the selected item, then draw the rectangle
// filled in blue. The item is selected when a bitwise And
// of the State property and the DrawItemState.Selected
// property is true.
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
e.Graphics.FillRectangle(Brushes.CornflowerBlue, e.Bounds);
}
else
{
// Otherwise, draw the rectangle filled in beige.
e.Graphics.FillRectangle(Brushes.Beige, e.Bounds);
}
DataRowView drv = (DataRowView)listBox1.Items[e.Index];
Image img = Image.FromFile(drv.Row["fullpath"].ToString());
img = img.GetThumbnailImage(e.Bounds.Width, e.Bounds.Height, null, IntPtr.Zero);
e.Graphics.DrawImage(img, e.Bounds.X, e.Bounds.Y);
// Draw the focus rectangle around the selected item.
e.DrawFocusRectangle();
}
I am not sure and this would have to be tested, but this article makes me believe that what I am going to say is true.
Basically, if you go by the MSDN documentation:
Occurs when a visual aspect of an owner-drawn ListBox changes.
So, this means that everytime an item is added, this event is called. Also, I think even when you perform certain drawing actions within this method, it will call itself (you might be able to avoid this using SuspendLayout and ResumeLayout on the listbox while updating), but am not sure.
Here is the kicker as far as I know it. Everytime this event is triggered, it is pretty much for every item in the list. (This might be useful in that you can de-color a previously selected item, so dont jump directly to what I am about to suggest without thinking it through). So, the DrawItemEventArgs has an index of the item being drawn. Using that, you can focus in on a specific item that you need to draw. That might help you from re-processing something that has already been processed (keep in mind the notes from the above article..copied below, about index being allowed to be -1).
To visualize this process:
Add 1 item ->DrawItem
Add 2nd item->DrawItem
->DrawItem
Add 3rd item->DrawItem
->DrawItem
->DrawItem
Add nth item->DrawItem * n
So, this actually results in a kind of fibonacci type situation (3 items resulted in 6 calls...5 would result in your 15 number), and you can see how the initial load could be cumbersome, and how adding a new item make for n calls to the method.
From the article above:
The ListBox calls the DrawItem method repeatedly, for each item in its
Items collection.
The DrawItemEventArgs argument to the DrawItem event handler exposes
an Index property whose value is the index of the item to be drawn.
Watch out! The system raises the DrawItem event with an index value of
-1 when the Items collection is empty. When that happens you should call the DrawItemEventArgs.DrawBackground() and DrawFocusRectangle()
methods and then exit. The purpose of raising the event is to let the
control draw a focus rectangle so that users can tell it has the
focus, even when there are no items present. The code traps for that
condition, calls the two methods, and then exits the handler
immediately
If a dialog box pops up over the top your control, guess what your control has to do when you close the dialog box? It has to redraw itself!
Try using Debug.Writeline to write debugging information when you are looking at drawing controls. Of course, you still need to be careful that you don't pull up Visual Studio over the top of your form as well! It's a situation where two monitors can really help.

Which method/function is called during PivotItem navigation WP7

I am just getting started on Windows phone 7 development and am stuck at this problem while using Pivot control:
I have 3 pivotitems and the swipe movement to navigate between pivots are working fabulously well, But the problem is...
I need to call a different function, say function1() when one pivotitem is visible and then call a function say function2() as soon as user swipes to another pivotitem.
Is there any delegate method which handles this..?
Thanks for your help!
You can handle the Pivot control's LoadingPivotItem event. This event passes PivotItemEventArgs which includes a property letting you know what pivot is about to be shown. Using this, you can then load the relevant controls and properties. For example,
private void pivotMain_LoadingPivotItem(object sender, PivotItemEventArgs e)
{
if (e.Item == pivotItem1)
{
//Load Pivot Item 1 stuff
}
if (e.Item == pivotItem2)
{
//Load Pivot Item 2 stuff
}
}
In the example above, pivotItem1 and pivotItem2 are the names I've given each PivotItem so you can give whatever names you want to each PivotItem and check if they're about to be shown. If you want to handle the event after the PivotItem has loaded, you can use the Pivot.LoadedPivotItem method.
If you want to know which PivotItem is currently being displayed at any time, you can use the Pivot.SelectedIndex method. It's zero-based, so the first PivotItem will have an index of 0, the second will have 1 and so on.
You can use SelectionChanged. In this function you'll be able to check to see which PivotItem is the SelectedItem and choose which function you want to call.

Bring Winforms control to front

Are there any other methods of bringing a control to the front other than control.BringToFront()?
I have series of labels on a user control and when I try to bring one of them to front it is not working. I have even looped through all the controls and sent them all the back except for the one I am interested in and it doesn't change a thing.
Here is the method where a label is added to the user control
private void AddUserLabel()
{
var field = new UserLabel();
userContainer.Controls.Add(field);
SendLabelsToBack(); // Send All labels to back
userContainer.Controls[field.FieldName].BringToFront();
}
Here is the method that sends all of them to the back.
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls);
label.SendToBack();
}
Yeah, there's another way. The Controls.SetChildIndex() also changes Z-order. The one with index 0 is the one on top. Doesn't buy you anything though, BringToFront() uses this method.
Your SendLabelsToBack() method as given cannot work, it will also send the label to added to the back. But your next statement fixes that again.
Okay, that doesn't work, which means the BringToFront() method doesn't get executed. Look in the Output window for a "first chance exception" notification. As written, your SendLabelsToBack() will cause an exception if the user control contains any control other than a UserLabel. Also, set a breakpoint after the BringToFront() call and check the value of userContainer.Controls[0].Name when it breaks.
Controls' z-index is per-container.
If you call BringToFront on a control that is inside a container (such as a Panel), it will not bring the container to the front.
Therefore, the control will only go in front of other controls in that container.
To see what containers your controls are in, you can use the Document Outline pane in the View menu.
EDIT: Your userContainer control is probably behind a different control.
Have you tried Invalidate() after BringToFront()? BringToFront does not raise the Paint event
try this:
private void SendLabelsToBack()
{
foreach (var label in userContainer.Controls)
{
label.SendToBack();
label.Invalidate();
}
}
I think you just need to change your last line:
userContainer.Controls[field.FieldName].BringToFront();
to this:
userContainer.Controls[field.Name].BringToFront();
When you use a string as the indexer for the Controls collection, it goes by the Name property of the control (not the FieldName property).
Since you're just trying to bring the most recently-added control to the top, this would also work:
userContainer.Controls[userContainer.Controls.Count - 1].BringToFront();
From my experience looks like windows puts all controls belonging to one graphic container(pane, group box...etc) in a software collection. The collection is ordered by child index which is a property of every control in that container.
The trick is that children with the same index can and do exists. In this case windows will paint those children ordered relative to others but between them it will paint them in the reverse order they had been added to the container.
Long story short: for one container-you need to make sure controls have different indexes by changing ALL NOT just SOME of the indexes when you want to change the z-order. For example:
foreach (Control newControl in TopControl.Controls)
{
TopControl.Controls.SetChildIndex(newControl,indexlogic(newControl));
}
where indexLogic(newControl ) is your method of calculation of the index of particular control.

Categories