Force pivot item to preload before it's shown - c#

I have a Pivot with several PivotItems, one of which contains a canvas that places its items in dynamic locations (depending on the data). I get the data, and I can place the items in their place before the user can choose this item (this isn't the first pivot). However, only when I select the PivotItem, the canvas renders itself, so you can see it flicker before it's shown as it should.
Is there a way to force the canvas to render before it's shown, so everything's prepared by the time the user sees it?
My code looks something like this:
In the page.xaml.cs:
private async void GameCenterView_OnDataContextChanged(object sender, EventArgs e)
{
// Load data...
// Handle other pivots
// This is the problem pivot
if (ViewModel.CurrentGame.SportTypeId == 1)
{
_hasLineups = ViewModel.CurrentGame.HasLineups.GetValueOrDefault();
HasFieldPositions = ViewModel.CurrentGame.HasFieldPositions.GetValueOrDefault();
// I only add the pivot when I need it, otherwise, it won't be shown
if (_hasLineups)
{
if (MainPivot.Items != null) MainPivot.Items.Add(LineupPivotItem);
}
if (HasFieldPositions)
{
// Here I place all the items in their proper place on the canvas
ArrangeLineup(ViewModel.TeamOneLineup, TeamOneCanvas);
ArrangeLineup(ViewModel.TeamTwoLineup, TeamTwoCanvas);
}
}
// Handle other pivots
}
private void ArrangeLineup(ObservableCollection<PlayerInLineupViewModel> teamLineup, RationalCanvas canvas)
{
if (teamLineup == null)
return;
foreach (var player in teamLineup)
{
var control = new ContentControl
{
Content = player,
ContentTemplate = LinupPlayerInFieldDataTemplate
};
control.SetValue(RationalCanvas.RationalTopProperty, player.Player.FieldPositionLine);
control.SetValue(RationalCanvas.RationalLeftProperty, player.Player.FieldPositionSide);
canvas.Children.Add(control);
}
}
The canvas isn't the stock canvas. I created a new canvas that displays items according to their relative position (I get the positions in a scale of 0-99).
The logic happens in the OverrideArrange method:
protected override Size ArrangeOverride(Size finalSize)
{
if (finalSize.Height == 0 || finalSize.Width == 0)
{
return base.ArrangeOverride(finalSize);
}
var yRatio = finalSize.Height/100.0;
var xRatio = finalSize.Width/100.0;
foreach (var child in Children)
{
var top = (double) child.GetValue(TopProperty);
var left = (double) child.GetValue(LeftProperty);
if (top > 0 || left > 0)
continue;
var rationalTop = (int) child.GetValue(RationalTopProperty);
var rationalLeft = (int) child.GetValue(RationalLeftProperty);
if (InvertY)
rationalTop = 100 - rationalTop;
if (InvertX)
rationalLeft = 100 - rationalLeft;
child.SetValue(TopProperty, rationalTop*yRatio);
child.SetValue(LeftProperty, rationalLeft*xRatio);
}
return base.ArrangeOverride(finalSize);
}
Thanks.

There are several tricks you could try. For example:
In your ArrangeOverride you can short-circuit the logic if the size hasn't changed since last time you executed (and the data is the same)
Make sure you're listening to the events on Pivot that tell you to get ready for presentation - PivotItemLoading for example
You can have the control not actually be part of the Pivot, but instead be in the parent container (eg a Grid) and have it with Opacity of zero. Then set it to 100 when the target PivotItem comes into view.

Related

How to have Top and Bottom toolbar of different colors in Xamarin.Forms

In one of the latest releases Xamarin.Forms added the possibility to place the toolbar at the bottom (ToolBarPlacement) and by setting the Property BarBackgroundColor on NavigationPage the background color of the toolbar can be changed.
Unfortunately, when the toolbar is split (as is the default on Windows 10 Mobile or when ToolbarPlacement is bottom) both bars have the same background color.
In my app I want to achieve that the top bar (with title and hamburger menu) has the system's accent color and the bottom bar (with commands and flyout) is gray, as this combination is also used by many system apps (e.g. Mail or Calendar on Windows 10 Mobile).
But I cannot figure out how to do it without touching the core implementation in Xamarin.Forms. I already tried custom NavigationPageRenderer and custom PageRenderer, but many of the relevant fields are private, sealed or internal or are accessing internal interfaces.
The background colors of the two bars seem to be bound to the same property as changing the background of one bar in Visual Studio's Live XAML Tree View also changes the color of the other one.
Any help on how to achieve the desired look will be appreciated.
Finally, I achieved the desired result.
One of the problems was that my RootPage was a MasterDetailPage, so I had to create a MasterDetailPageRenderer. Also I assumed that Xamarin would use the actual UWP Page's TopAppBar and BottomAppBar properties. This is not the case.
With the following MasterDetailPageRenderer the top bar (with hamburger menu button and title) is tinted green while the bottom bar stays the default gray (basically the renderer just removes the Background binding of the StackPanel representing the top bar and sets it to Green). One problem was, that the FindName and FindByName methods were not working (always returned null), so I had to roll my own implementations using the VisualTreeHelper.
[assembly: ExportRenderer(typeof(MasterDetailPage), typeof(CustomMasterDetailPageRender))]
public class CustomMasterDetailPageRender : MasterDetailPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<MasterDetailPage> e)
{
base.OnElementChanged(e);
if (Element != null)
{
Element.Appearing += Element_Appearing;
}
}
private void Element_Appearing(object sender, EventArgs e)
{
(sender as MasterDetailPage).Appearing -= Element_Appearing;
if (Control != null)
{
var topBarArea = FindElementByName(Control, "TopCommandBarArea");
if (topBarArea != null)
{
var topContent = FindElementByType<StackPanel>(topBarArea);
if (topContent != null)
{
topContent.Background = new SolidColorBrush(Colors.Green);
}
}
}
}
static DependencyObject FindElementByName(DependencyObject parent, string name)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var sub = VisualTreeHelper.GetChild(parent, i);
if (sub is FrameworkElement)
{
if (((FrameworkElement)sub).Name == name)
{
return sub;
}
}
var r = FindElementByName(sub, name);
if (r != null)
return r;
}
return null;
}
static T FindElementByType<T>(DependencyObject parent)
where T: DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var sub = VisualTreeHelper.GetChild(parent, i);
if (sub is T)
{
return (T)sub;
}
var r = FindElementByType<T>(sub);
if (r != null)
return r;
}
return null;
}
}

Remove Specific LineGeometry in a Path

My application consists of a canvas with some user drag-droppable UIelements which can be further connected using lines.
To connect two UIelements I have used a Path consisting a GeometryGroup, which further contains LineGeometry as its childs. Check this screenshot.
So as in the picture, the three items are connected via a Path which consists of 2 LineGeometry. I'm trying to implement 'Remove Link' option but all i can do is remove the whole Path, which would remove both the LineGeometry. How can i specifically select that particular line segment and remove it?
There is a solution without implementing math for hit testing. For example if you want to remove a line by mouse down:
<Canvas Mouse.MouseDown="Canvas_MouseDown">
where Canvas_MouseDown should be implemented like this:
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
var canvas = sender as Canvas;
if (canvas == null)
return;
// 1. Find a Path containing links
HitTestResult hitTestResult = VisualTreeHelper.HitTest(canvas, e.GetPosition(canvas));
var path = hitTestResult.VisualHit as Path;
if (path == null)
return;
// 2. Iterate through geometries of the Path and hit test each one
// to find a line to delete
var geometryGroup = path.Data as GeometryGroup;
if (geometryGroup == null)
return;
GeometryCollection geometries = geometryGroup.Children;
Point point = e.GetPosition(path);
var pen = new Pen(path.Stroke, path.StrokeThickness);
var lineToDelete = geometries.OfType<LineGeometry>()
.FirstOrDefault(l => l.StrokeContains(pen, point));
// 3. Delete link
if (lineToDelete != null)
geometries.Remove(lineToDelete);
}
You need the distances to the click point.
public void RemoveLink(Point point)
{
// ...
// point - 2D click point
// lineList - list of links (lines)
setDistance(lineList, point)
lineList.Sort(compare);
lineList[0].remove();
// ...
}
private static Comparison<GeomObject> compare = new Comparison<GeomObject>(GeomObject.CompareByDistance);
public static int CompareByDistance(GeomObject go1, GeomObject go2)
{
return go1.mDistance.CompareTo(go2.mDistance);
}
private void setDistance(List<Line> lineList, Point point) {
// set mDistance for each Line
// mDistance - distance to point
}

Allow a ListBox to overlap a TableLayoutPanel (C# .NET)

I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.

Freeze first tab in C#.net tab control

Is there a way to freeze the first tab of a C#.net tab control?
I have a tab controller which can have many tabs. If the user is scrolling them the first tab should remain in the first position and rest of the tabs should move.
I have tried to do it using removing and inserting the tab in paint method. But seems like it is getting index issues some times when I tried to remove and add the first page.
// For the first time home tab comes as the first tab item
if (this.homeTab == null)
{
this.homeTab = this.TabPages[0];
}
// get initial first display index to a temp variable
int tempFirstIndex = -1;
for (int index = 0; index < this.TabCount; index++)
{
Rectangle currentTabBounds = this.GetTabTextRect(index);
if (currentTabBounds != Rectangle.Empty && tempFirstIndex < 0 && currentTabBounds.X >= 0)
{
tempFirstIndex = index;
break;
}
}
int homeTabIndex = this.TabPages.IndexOf(this.homeTab);
Rectangle homeTabBounds = this.GetTabTextRect(homeTabIndex);
if (homeTabIndex > tempFirstIndex)
{
this.TabPages.Remove(this.homeTab);
this.TabPages.Insert(tempFirstIndex, this.homeTab);
}
else
{
// find the first visible position
// it can not be simply the tempFirstIndex, because in this scenario tab is removed from begining
// tabs are not same in width
while (homeTabBounds != Rectangle.Empty && homeTabBounds.X < 0)
{
homeTabIndex++;
this.TabPages.Remove(this.homeTab);
this.TabPages.Insert(homeTabIndex, this.homeTab);
homeTabBounds = this.GetTabTextRect(homeTabIndex);
}
}
I have figured out a way to freeze the first tab.
The problem with the above solution is, I tried to manipulate the tab page list inside the paint method. It causes to call the paint method over and over again and generates exceptions.
So I have tried to manipulate the tab position returned from "this.GetTabTextRect(index)". It worked fine. But I had to write some code to adjust the tab selection logic as well.
protected Rectangle GetTabTextRect(int index)
{
// gets the original tab position
Rectangle tabBounds = this.GetTabRect(index);
// first displayed index will be calculated in the paint method
// if it is equal or less than to zero means, the number of tabs are not exceeded the display limit. So no need tab manipulating
if (this.firstDisplayedIndex > 0 )
{
// if it is not first tab we adjust the position
if (index > 0)
{
if (index > this.firstDisplayedIndex && index <= this.lastDisplayedIndex)
{
Rectangle prevBounds = this.CurrentTabControl.GetTabRect(this.firstDisplayedIndex);
// home tab (first tab) bounds will be stored in a global variable when the tab control initializes
tabBounds.X = tabBounds.X - prevBounds.Width + this.homeTabBounds.Width;
}
else if (index == this.firstDisplayedIndex) // we need to free this slot for the first tab (index = 0)
{
tabBounds.X -= 1000;
}
}
else
{
// first tab position is fixed
tabBounds.X = 0;
}
}
}

How does one get a ListView (GridView) cell's contents when clicked with a mouse

I am trying to get the text value of a "cell" inside of a GridView that is set as the view of a ListView. I do not want to get the SelectedItem of the ListView as that just returns my entire View Model (but not which property the cell refers to).
I am able to get the text value by responding to direct mouse events (up down or whatever) and if the value is a textblock, obviously I can use the text. This works great and as of right now this is my only solution, although its currently limited.
I would like to take it a step further and be able to click anywhere with in the cell area, navigate around to find the appropriate textblock and then use that value. I have tried a half million ways to do this but what seems logical doesn't seem to quite work out like it should.
Setup:
I have a dynamic GridView that creates its own columns and bindings based on data models that I pass to it. I am using a programmatic cell template (shown below) to have individual control over the cells, particularly so I can add a "border" to it making it actually separate out each cell. I have named the objects so I can access them easier when I'm navigating around the VisualTree.
Here is the Template Code. (Note that the content presenter originally was a textblock itself, but this was changed for later flexibility)
private DataTemplate GetCellTemplate(string bindingName)
{
StringBuilder builder = new StringBuilder();
builder.Append("<DataTemplate ");
builder.Append("xmlns='http://schemas.microsoft.com/winfx/");
builder.Append("2006/xaml/presentation' ");
builder.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
builder.Append("xmlns:local = 'clr-namespace:XXXXXXXX");
builder.Append(";assembly=XXXXXXXXX'>");
builder.Append("<Border Name=\"border\" BorderThickness=\"1,0,0,0\" BorderBrush=\"Gray\" Margin=\"-6,-3,-6,-3\">");
builder.Append("<Grid Margin=\"6,3,6,3\">");
builder.Append("<ContentPresenter Name=\"content\" HorizontalAlignment=\"Stretch\" Content=\"{Binding ");
builder.Append(string.Format("{0}", bindingName));
builder.Append("}\"/>");
builder.Append("</Grid>");
builder.Append("</Border>");
builder.Append("</DataTemplate>");
DataTemplate cellTemplate= (DataTemplate)XamlReader.Parse(builder.ToString());
return cellTemplate;
}
What I have Tried:
The logical approach for me was to react to a Mouse event. From the object that had the mouse event I would do either
A. Look at its children to find a textblock, or
B. Get its parent then look for child with a textblock.
My assumption is that if I click in white space I'm clicking in a container that has my textblock. So far the two things that come up are a Border and a Rectangle (if I don't click the text itself). A. Returns absolutely nothing except for the recangle and the border. When I do B i can find textblocks but they are every single text block in the entire row.
So what I try to do from that is get all textblocks, then go backwards till I find which one has a IsMouseOver property as true. It turns out none of these objects EVER have a IsMouseOver except the content presenter for the entire row. So this seems to indicate to me is that the whitespace in the cells does not actually contain the textblock.
What I find is that when I click on the Border and start looking at children, I eventually get to a container that has a rectangle (the rectangle I click) and a grid row view presenter. The presenter shows all of the objects inside the row (hence why i would get all textblocks when i do this recursive scan).
Here is some of the code used to do this to get an idea of what i'm doing. I have written about 10 different versions of this same recursive code generally attempting to find who has the Mouse over it and is related to a textbox.
private void OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
object original = e.OriginalSource;
if (original is TextBlock)
{
this.valueTextBlock.Text = ((TextBlock)original).Text;
}
else if (original is FrameworkElement)
{
var result = GetAllNestedChildren<Border>(VisualTreeHelper.GetParent((DependencyObject)original)).Where(x => x.Name == "border").Where(x => HasAChildWithMouse(x)).ToList();
}
else
{
this.valueTextBlock.Text = string.Empty;
}
}
private bool HasAChildWithMouse(UIElement element)
{
if (element.IsMouseOver || element.IsMouseDirectlyOver)
return true;
var childCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(element, i);
if (child is UIElement)
if (HasAChildWithMouse((UIElement)child))
return true;
}
return false;
}
private IEnumerable<T> GetAllNestedChildren<T>(DependencyObject obj) where T : UIElement
{
if (obj is T)
yield return obj as T;
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
foreach (var nested in GetAllNestedChildren<T>(child))
yield return nested;
}
}
private T GetObjectByTypeParentHasMouse<T>(DependencyObject obj) where T : UIElement
{
if (obj is T)
{
if ((VisualTreeHelper.GetParent(obj) as UIElement).IsMouseOver )
{
return obj as T;
}
}
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
var correctType = GetObjectByTypeParentHasMouse<T>(child);
if (correctType != null)
return correctType;
}
return null;
}
private T GetContainedType<T>(DependencyObject obj, bool checkForMouseOver) where T : UIElement
{
if (obj is T && ((T)obj).IsMouseOver)
return obj as T;
var childCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childCount; ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
var correctType = GetContainedType<T>(child, checkForMouseOver);
if (correctType != null)
return correctType;
}
return null;
}
The other approach I took was to start with the TextBlock itself, find its containing parent and find out how i can navigate to the answer. I find the templateparent is the ContentPresenter (named ="content") I find the grid, and then the border. The parent of the border is a content presenter whos content is the data view model for the entire row. The parent of this contentpresenter is the grid column's presenter. This is the same one that i was navigating up to in the other one.
It would appear that the first approach objects while are contain the cell do not actually contain the textblock or the entire cell templated items. It would appear to me there is no way to go from the Border or Rectangle that is clicked, back to the actual text field.
"Long story short" is there ANY way to make this connection?
(Btw I am not willing to give up this ListView/GridView because its payoffs far outweigh this negative and I'd gladly give up on this idea to keep the rest).
I think you sjould be able to either
1) Add some kind of (toggle)button to the root of your data template, and either bind to Command and handle it on your viewmodel or bind to IsChecked/IsPressed and handle changes via data triggers or w/e on the view side.
2) Add EventTrigger to your datatemplate at some point, and handle PreviewNouseUp/Down events there via simple animations.

Categories

Resources