I've come across an issue using the Pivot control with Caliburn Micro and WP8. When I update DisplayName for the child ViewModels (pivot items), the spacing of the pivot headers does not update to reflect this. Thus, pivot headers overlap each other, and it looks very jumbled.
Example:
WP7 Version:
WP8 Version:
This problem only started when we migrated from targetting WP7/WP8 to WP8 only. Does anyone have any ideas about how to get the spacing to update when changing DisplayName, and thus the pivot header text? Thanks!
I know a long time has passed since you asked this, but I got a solution.
Microsoft is promising that this issue is fixed in WP8, but it still isn't.
In order to render the headers properly, you have to change the width of the pivot item.
So here is an example code width x:Name="PivotControl" set for Pivot:
double width = PivotControl.Width;
double width2 = Application.Current.Host.Content.ActualWidth+10;
PivotControl.Width = width2;
await Task.Delay(1);
PivotControl.Width = width;
This will change the width to a bit more than yoru screen width and than after 1ms change it back so the items will be rendered properly and the Layout will be updated (because LayoutUpdate is not working).
Here is better solution
Pivot header template:
<!-- The SizeChanged event is key here. We have to invalidate an ancestor when we get this event. -->
<DataTemplate x:Key="pivotHeaderTemplate">
<TextBlock SizeChanged="OnHeaderSizeChanged" Text="{Binding SomeData}"/>
</DataTemplate>
Pivot:
<phone:Pivot Title="WHATEVER"
DataContext="{StaticResource query}"
ItemsSource="{Binding source}"
HeaderTemplate="{StaticResource pivotHeaderTemplate}"
ItemContainerStyle="{StaticResource pivotItemStyle}"/>
Code-behind:
/// <summary>
/// When something inside of the template changes, then we need to invalidate the measure of the ancestor PivotHeadersControl
/// </summary>
private void OnHeaderSizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement fe = (FrameworkElement)sender;
while (fe != null)
{
if (fe is PivotHeadersControl)
{
fe.InvalidateMeasure();
break;
}
fe = VisualTreeHelper.GetParent(fe) as FrameworkElement;
}
}
Related
I have a CarouselView in which I call an object with individual and completely different Views.
The only elements my ContentPage has, are the CarouselView itself, and a bottombar with a gradient above it (notice gradient in following image).
I have done this in a way in which this gradient dissapears when the page's scrolling space becomes 0 (when I have scrolled to the end of the page).
The problem is that when I swipe between items in the CarouselView, the CarouselView always maintains the height of the very first View that is called in.
This means that, in a View with MORE height than the 1st one, when scrolling up (after being at the very bottom, and therefore not showing a gradient) the gradient will only show again once it hits the height value of the 1st page.
In a View with LESS height than the 1st one, the page will allow me to scroll down until I reach the height value of the 1st page, even if there are not enough elements on the page to even need a scroll.
Essentially, what I am asking for, is if there is a way in which I can, in some way, "refresh" the height of the Page every time a scroll is complete to another View in the CarouselView, resolving my height issues in smaller views, and my gradient issues in larger views.
Main ContentPage Code Behind (Gradient)
public double ScrollingSpace
{
get
{
return MainScrollView.ContentSize.Height - MainScrollView.Height;
}
set { }
}
// Removes gradient when scroll is complete
private void OnScrolled(object sender, ScrolledEventArgs e)
{
if (ScrollingSpace <= e.ScrollY) // Touched bottom
EndPageGradient.SetValue(IsVisibleProperty, false); // the view is GONE, not invisible
else
EndPageGradient.SetValue(IsVisibleProperty, true);
}
// Removes gradient if page is not large enough to need scroll
protected override void OnAppearing()
{
if (ScrollingSpace <= 0)
EndPageGradient.SetValue(IsVisibleProperty, false); // the view is GONE, not invisible
}
Main ContentPage CarouselView XAML
<CarouselView
ItemsSource="{Binding ViewList}"
Loop="False">
<CarouselView.ItemTemplate>
<DataTemplate>
<ContentView Content="{Binding .}" />
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
Main ContentPage ViewModel (List with Views for CarouselView)
ViewList = new List<ContentView>()
{
new Step1(),
new Step2(),
new Step3(),
new Step4(),
new Step5(),
new Step6(),
new Step7(),
new Step8()
};
Thanks in advance!
In my Xamarin Forms application I have Label:
<Label Text="{Binding Number}" HorizontalTextAlignment ="Center">
In ViewModel:
private int _number;
public int Number
{
get { return _number; }
set { SetProperty(ref _number, value); }
}
When message is received, I update number:
MessagingCenter.Subscribe<EmptyMessage>(this, "NUMBER_CHANGED", OnNumberChanged);
private async void OnNumberChanged(EmptyMessage emptyMessage)
{
Number = ReadNumberFromDB();
}
All of this is on MainPage. When "NUMBER_CHANGED" message is triggered from MainPage, Number is updated on UI and label containing Number is properly centered.
From MainPage, one can PushAsync SecondPage. From SecondPage "NUMBER_CHANGED" can also be triggered. After triggering it and returning to MainPage, Number is updated, label containing it has proper value but instead of being centered it is left aligned.
Is this Xamarin UI bug? Is there a workaround for this? I need somehow to tell label to refresh it's position. I want label to always be centered. I don't know why it is left aligned after refreshing it's content.
HorizontalTextAlignment and VerticalTextAlignment, not working after a data change is a known bug. Bug 55359 details this for a TabbedPage, but it occurs in many places, such as Bug 49311.
The workaround is to use HorizontalOptions or VerticalOptions for the moment.
I ran into this problem recently. I simply made a constant variable to track what page needed to be updated OnResume and just reassigned my bindings accordingly.
I'm using pivot control to display a large number of images (about 300). I thought of just using 3 pivot item, and when user swipes, change either pivot item or update item source. But I don't know how to do this efficiently ?
Or is there a way of using gesture and stimulating swipe effect as the pivot does ? Something like transition ?
You can use normal Image Control with gesture Manipulation events to swipe left to right and right to left for previous/next photos.
Please find the code below.
XAML Code
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Margin="0">
<Image Margin="0" x:Name="ImagePanel" Source="{Binding SelectedPhoto.PhotoURL}" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
C# code
public SlideShow()
{
// Tag ManipulationCompleted event for the current page in the constructor.
ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(SlideShow_ManipulationCompleted);
}
// ManipulationCompleted event. Update the Previous/next photo based on the swipe direction.
void SlideShow_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
var manipEndPoint = e.TotalManipulation.Translation;
const int threshold = 100;
if ((manipEndPoint.X > _manipStartPoint.X) && ((manipEndPoint.X - _manipStartPoint.X) > threshold))
{
LoadPreviousPhoto();
}
else if ((manipEndPoint.X < _manipStartPoint.X) && ((_manipStartPoint.X - manipEndPoint.X) > threshold))
{
LoadNextPhoto();
}
}
Let me know if you need any more help.
Thanks,
Kamal.
I have created a Scrollviewer in WP7, which harbors 3 usercontrol, each one of which hold as their content XAML created UserControls. This works fine. This scrollviewer should be able to scroll between these items, but make this not possible for the user to scroll. So when an item in one of these contents are clicked upon, the scrollviewer slides left or right depending on the item selected, and bring into view one of the other usercontrols. I use a mediator to accomplish this:
<Grid.Resources>
<Storyboard x:Name="ItemAnimation">
<DoubleAnimation x:Name="ItemAnimationContent"
Storyboard.TargetName="Mediator"
Storyboard.TargetProperty="ScrollableWidthMultiplier"/>
</Storyboard>
</Grid.Resources>
<ScrollViewer Name="ScrollableItemPanel"
Grid.Row="2"
Grid.RowSpan="3"
Grid.ColumnSpan="3"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled">
<StackPanel Orientation="Horizontal">
<UserControl Name="NewsListBoxControl" Width="480" />
<UserControl Name="DetailedItemControl" Width="480"/>
<UserControl Name="ExternalBrowserItemControl" Width="480"/>
</StackPanel>
</ScrollViewer>
<local:ScrollableItemAnimationMediator x:Name="Mediator"
ScrollViewer="{Binding ElementName=ScrollableItemPanel}"/>
In basic, this works fine too, I can navigate between the items, and load upon them the content as usercontrols. But the problem lies in granting the user the abillity to scroll. Before the item scrolls, I set the hittestvisibilty to true, and the horizontalscrollbarvisibility to visible. After the animation is done, I want to grant back the hittestvisibility and set the horizontalscrollbarvisibility to Disabled again. This latter is where the problem is: when I set the horizontalscrollbarvisibility to Disabled, the scrollviewer automatically brings back into view the first of three items in the stackpanel. How can I stop this? This is the code I use to scroll the mediator:
private void CreateDetailedArticleItem( Dictionary<string, string> itemQuery )
{
_articleDetailPage.ItemQuery = itemQuery;
DetailedItemControl.Content = _articleDetailPage as UserControl;
Animate( _articleDetailPage, 0.0f, 0.5f, 250 );
}
private void Animate( IContentControl control, float from, float to, double milliseconds )
{
//this eventhandler will fire when the animation has completed
EventHandler handler = null;
//we take away the User Input just for the moment, so that we can animate without the user interfering. Also, we make horizontalScroll Visible
IsUserEnabled = false;
//we then set the content of the animation. Where from will it move, towards where and in what duration?
ItemAnimationContent.From = from;
ItemAnimationContent.To = to;
ItemAnimationContent.Duration = TimeSpan.FromMilliseconds( milliseconds );
//we start the animation
ItemAnimation.Begin( );
//we tell the new control that it will appear soon, so it can load its main content
control.ViewWillAppear( );
//also, we tell the currentcontrol that it will disappear soon, so it can unload its content and eventhandlers and so on
CurrentControl.ViewWillDisAppear( );
//the handler is a delegate. This way, it becomes rather easy and clean to fire the completed event, without creating a strong reference ( well, actually,
//we do create a strong reference, but as soon as it is fired, we remove it again, shhhh! ).
handler = delegate( object sender, EventArgs e )
{
//as stated, we remove the eventlistener again, so it won't keep firing all the time
ItemAnimation.Completed -= handler;
//after the animation, we tell the new control that it is now in screen, and can start downloading its data
control.ViewDidAppear( );
//at the same time, the "current" control has fully moved out of view, so it can now fully unload all its content.
CurrentControl.ViewDidDisAppear( );
//now, all we have to do is to make sure that the next time an item is being loaded, the new content is spoken to, not the old one
CurrentControl = control;
//and finally, enable the users input again, and remove the horizontal scrollbarvisibility
IsUserEnabled = true;
};
ItemAnimation.Completed += handler;
}
private bool IsUserEnabled
{
set
{
//when the user can control the scrollviewer, then the horizontal scrollvisibility is disabled, so that the user cannot move horizontally,
//otherwise, so we only make it visible when the program needs to animate.
ScrollableItemPanel.IsHitTestVisible = value;
ScrollableItemPanel.HorizontalScrollBarVisibility = value ? ScrollBarVisibility.Disabled : ScrollBarVisibility.Visible;
}
}
I had already asked this question, then regarded it as answered, as I thought it to be answered, namely using ScrollbarVisibility.Hidden instead of ScrollbarVisibility.Disabled, only the scrollbarvisibility stays visible this way, and the user can still scroll. Is there a native way to deal with this problem?
Any help would be greatly appreciated. Greetz
Rather than fight the behavior of the native control it may be easier to just manipulate the position of items yourself using a custom control (wrapping your other controls) which animates between different visual states (adjust the translate transform) depending on the "selected" item.
I'm currently writing a portrait only app, but I have a customer requirement that they'd like to implement a special feature if the phone is turned on its side.
To be clear they don't want the page to change orientation - so keeping the page as portrait works well here - but they do want to be able to detect the sideways change.
Is there anyway of finding this out (e.g. from rootframe or from some other object?) or do I have to access the Accelerometer data and work it out myself?
To be clear on this...
I'm trying to keep the page in portrait at all times.
and if I specify SupportedOrientations="portraitorlandscape" then keeping the page in portrait seems to be hard (correct me if I'm wrong, but it just doesn't seem to want to stay in portrait - the MS SDK is too good at making the page go landscape)
and if I don't specify SupportedOrientations="portraitorlandscape" then I don't get calls to OnOrientationChanged in either the page or the RootFrame
And as the icing on the cake... I need the phone to stay in portrait mode too - I need the SystemTray to stay at the top of the screen (the portrait top).
You can handle the OnOrientationChanged event which will return a PageOrientation enumeration.
Accepting this because of the comments:
#Stuart - You may find the Orientation Helper class in this starter kit useful. It uses the accelerometer, so I guess you'll have to use that, but it might save you time rolling out your own version: http://msdn.microsoft.com/en-us/library/gg442298%28VS.92%29.aspx#Customizing_Behavior
This might help, but for a case when arriving to this particular page, not for the initial page - so only partly answering the question. It trigs the OnOrientationChanged although no change has been done! (Figured out this solution after having tried to find a solution for two days) :
On the particular page, write in . xaml code
Orientation="None"
On the .xaml.cs side, write under
InitializeComponent();
Orientation = this.Orientation;
this.OrientationChanged += new EventHandler<OrientationChangedEventArgs>
(OnOrientationChanged);
and separately
void OnOrientationChanged(object sender, OrientationChangedEventArgs e)
{
if ((e.Orientation & PageOrientation.Landscape) != 0)
{
MyImage.Height = 480; //for example
}
{
MyImage.Width = 480; // for example
}
}
In my case, I placed the image as follows:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel>
<Image x:Name="MyImage"/>
</StackPanel>
.. followed by other code, still loading during which time picture is shown ...
This decreases the size of the image when in Landscape mode when entering the page!
Got the solution, finally, after having seen Jeff Prosises site
Detect orientation change:
http://alan.beech.me.uk/2011/04/19/detecting-orientation-change-wp7dev/
I had to do a similar thing in one of my apps before where an image that is used as the background doesnt rotate but other items on the page do.
The code looks a bit like this:
protected override void OnOrientationChanged(OrientationChangedEventArgs e)
{
// Keep the image in the same position as in portrait
// But still allows other controls to rotate when orientation changes.
switch (e.Orientation)
{
case PageOrientation.LandscapeRight:
ForegroundImage.RenderTransform = new CompositeTransform { Rotation = 90 };
ForegroundImage.RenderTransformOrigin = new Point(0.5, 0.5);
ForegroundImage.Margin = new Thickness(158.592, -158.792, 158.592, -160.558);
break;
case PageOrientation.LandscapeLeft:
ForegroundImage.RenderTransform = new CompositeTransform { Rotation = 270 };
ForegroundImage.RenderTransformOrigin = new Point(0.5, 0.5);
ForegroundImage.Margin = new Thickness(158.592, -158.792, 158.592, -160.558);
break;
default: // case PageOrientation.PortraitUp:
ForegroundImage.RenderTransform = null;
ForegroundImage.RenderTransformOrigin = new Point(0, 0);
ForegroundImage.Margin = new Thickness();
break;
}
base.OnOrientationChanged(e);
}
Unfortunately there's no real work around for the system tray or app bar. For the system tray you could hide this though and then only show it (for a period of time) when the user taps or swipes near that part of the screen.