I wanted to add a scroll down prompt to ScrollView in my Xamarin app, for which I followed this article.
What it actually does is that it adds a scroll down indicator (Label) outside Scrollview, so if the text is more than the size of the page it shows this indication, written "Scroll down for more!", and when scrolling completely to the end, it hides this indicator.
This part is working, but if the text is less than the page, it still shows this scroll down indicator.
I want that if the text is less than the page, the visibility of the scroll down indicator should be false.
.xaml
<Grid>
<ScrollView x:Name="MyScrollView" Scrolled="MyScrollView_Scrolled">
<StackLayout>
<BoxView BackgroundColor="Red" HeightRequest="128"/>
<BoxView BackgroundColor="Orange" HeightRequest="128"/>
</StackLayout>
</ScrollView>
<Label x:Name="ScrollDownIndicator"
Text="Scroll down for more!"
BackgroundColor="Black"
TextColor="White"
FontSize="Large"
HorizontalTextAlignment="Center"
VerticalOptions="End"/>
</Grid>
.xaml.cs
private void MyScrollView_Scrolled(object sender, ScrolledEventArgs e)
{
double spaceAvailableForScrolling = MyScrollView.ContentSize.Height - MyScrollView.Height;
double buffer = 32;
ScrollDownPrompt.IsVisible = spaceAvailableForScrolling > e.ScrollY + buffer;
}
Juggling around with your code i came to a solution that might get you where you want.
Just looking for the fix? go and read Solution section. If you want to know the why, go ahead and read the Explanation section.
Solution
First of all, i realized that in the code you posted the Label in Xaml file is called ScrollDownIndicator, but in the event handler you are changing a variable called ScrollDownPrompt instead. On my tests i went ahead to equalize both names...
In your .xaml.cs file, add the following lines to your page constructor:
public MainPage()
{
InitializeComponent();
MyScrollView.PropertyChanged += (s, a) =>
{
if (a.PropertyName == ScrollView.ContentSizeProperty.PropertyName)
{
double buffer = 32;
double spaceAvailableForScrolling = MyScrollView.ContentSize.Height - MyScrollView.Height;
ScrollDownIndicator.IsVisible = spaceAvailableForScrolling > buffer;
}
};
}
private void MyScrollView_Scrolled(object sender, ScrolledEventArgs e)
{
double spaceAvailableForScrolling = MyScrollView.ContentSize.Height - MyScrollView.Height;
double buffer = 32;
ScrollDownPrompt.IsVisible = spaceAvailableForScrolling > e.ScrollY + buffer;
}
Explanation
In your code you only change the visibility of the Indicator from the event handler: MyScrollView_Scrolled. If no scrolling is performed, the event handler is not called and no visibility of the indicator is checked: it is simply always visible.
To correct this behavior you have to check the visibility when the ContentSize property of ScrollView changes.
Hope this helps some people :D
Related
-> xamarin forms resize listview (inside stacklayout) after each keyboard entry - from code behind
(would like this cross platform if it is possible?)
Using SearchBar to allow user to search for products of wines... when text is changed the displayed results are updated correctly but I want to change the size of the ListView to only show results removing any empty white space.
So for example in attached picture entry 'Wine ' returns 12 results, which shows all....but 'Wine 1' only 4 results. So I would like the ListView to end after 'wine 12'(from the results) instead the opacity covers the rest of the screen.
Have tried some examples such as:
ListView inside StackLayout: How to auto resize the ListView?
but still cant get it going does anyone see what I am doing wrong thank you
<NavigationPage.TitleView>
<StackLayout HorizontalOptions="StartAndExpand" Orientation="Horizontal">
<SearchBar x:Name="SearchBar" TextChanged="SearchBar_TextChanged" HorizontalOptions="FillAndExpand" Placeholder="Search..." PlaceholderColor="Gray" TextColor="White" VerticalOptions="StartAndExpand"/>
</StackLayout>
</NavigationPage.TitleView>
private void SearchBar_TextChanged(object sender1, TextChangedEventArgs e1)
{
StackSearchResults.IsVisible = true;
int numberOfProducts = 0;
SearchListView.ItemsSource = GetProducts(out numberOfProducts, e1.NewTextValue);
SearchListView.RowHeight = 50;
SearchListView.PropertyChanged += (object sender, System.ComponentModel.PropertyChangedEventArgs e) =>
{
if (e.PropertyName == "ItemsSource")
{
try
{
if (SearchListView.ItemsSource != null)
{
SearchListView.HeightRequest = numberOfProducts * 50;
}
}
catch (Exception ex)
{
}
}
};
}
you only need to assign the PropertyChanged handler once, not every time TextChanged fires. That is probably not the root issue, but it's not helping. You should also be sure you're assigning the HeightRequest on the UI thread
XAML File Content
<StackLayout>
<!-- Place new controls here -->
<Label Text="First app in c#"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<ProgressBar x:Name="pbstatus" ProgressColor="LightGreen" Progress="0.01"/>
</StackLayout>
Actual Code (initializing variables)
int countvar = 0;
string buttoncontains = "Click Me";
ProgressBar progressBar = new ProgressBar { ProgressColor = Color.LightGreen, Progress = 0 };
The Progressbar above the buttons is the one made by the xaml file,
the otherone created in the code is not shown or binded to the element,
for any reason i cant use the progressbar made in the xaml file.
If you want to add Control by C# to the layout, you can give a name for your StackLayout in xaml.
<StackLayout x:Name="sl">
<!-- Place new controls here -->
<Label x:Name="myLabel" Text="First app in c#"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<ProgressBar x:Name="pbstatus" ProgressColor="LightGreen" Progress="0.01" />
</StackLayout>
Then, add the control(used sl.Children.Add()) by C# in the backend code,
string buttoncontains = "Click Me";
Button button = new Button() {Text= buttoncontains };
button.Clicked += Button_Clicked;
ProgressBar progressBar = new ProgressBar { ProgressColor =
Color.LightGreen, Progress = 0 };
sl.Children.Add(button);
sl.Children.Add(progressBar);
Button click event
private void Button_Clicked(object sender, EventArgs e)
{
pbstatus.Progress = pbstatus.Progress+0.01;
}
I bind the Button's text(Button created by C#) to the pbstatus's Progress(pbstatus created by xaml),
I used following code.
button.BindingContext = pbstatus;
button.SetBinding(Button.TextProperty, "Progress");
here is running gif.
Here is a helpful article about binding, you can refer to it.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/basic-bindings
I want to make a comment entry that is only visible if the user is at the bottom of the article.
So the app has to recognize when the user has scrolled enough, then a method should make the entryfield visible.
I can't find something like this on the Internet, so maybe you guys can help me.
This one is without the entryfield and when the user scrolls down ...
... the entryfield becomes visible
If you are using a ScollView, there is a Scrolled event that fires whenever the view is scrolled and the ScrolledEventArgs contain ScrollX and ScrollY properties that allow you to know where the ScrollView currently is. If you compare ScrollY to the height of the ContentSize property of the ScrollView, e.g.:
XAML:
<StackLayout>
<ScrollView x:Name="scrollView" Scrolled="Handle_Scrolled">
<StackLayout>
<Label Text="{Binding Article}" HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand" />
</StackLayout>
</ScrollView>
<Entry IsVisible="{Binding AtEnd}" Placeholder="End reached!" />
</StackLayout>
Code behind (MainPage is a ContentPage subclass):
string _article;
public string Article
{
get
{
return _article;
}
set
{
if (_article != value)
{
_article = value;
OnPropertyChanged("Article");
}
}
}
bool atEnd;
public bool AtEnd
{
get
{
return atEnd;
}
set
{
if (atEnd != value)
{
atEnd = value;
OnPropertyChanged("AtEnd");
}
}
}
public MainPage()
{
Article = "<put in enough text here to force scrolling>";
AtEnd = false;
InitializeComponent();
BindingContext = this;
}
void Handle_Scrolled(object sender, Xamarin.Forms.ScrolledEventArgs e)
{
if (e.ScrollY + scrollView.Height >= scrollView.ContentSize.Height)
AtEnd = true;
else
AtEnd = false;
}
That said, why not just put the entry below the article using the same scroll view? IOW just put the Entry element after the Label above in the same StackLayout and the Entry will just be there at the end always, but the user won't see it until they scroll down. Seems that that would be a simpler solution. Of course you may not be using a Label but the same applies, just put the Entry at the bottom of the layout that the ScrollView is scrolling.
I have a Windows Store-style WPF application, and I just added search to it. When I click the Search button in the app bar, I set my FlyoutPresenter containing the SearchBox to Visible. This button is placed in the lower right-hand corner. It works good on computers with keyboards, but I ran into a problem when the virtual keyboard, or InputPane, opens. First, the keyboard covered the box. I solved that problem by checking and adjusting the margin of the box when the box is in focus, but when I scroll the page to the very top and bottom, the control starts moving on the page. Here is my minimal code:
XAML:
<Grid Background="White" x:Name="MainGrid">
<!-- App Bar with Search button -->
<AppBar x:Name="BAppBar" VerticalAlignment="Bottom">
<CommandBar>
<CommandBar.PrimaryCommands>
<AppBarButton Icon="Find" Label="Search" Click="Search_Click"/>
</CommandBar.PrimaryCommands>
</CommandBar>
</AppBar>
<!-- Search button and Close button -->
<FlyoutPresenter VerticalAlignment="Top" Name="SearchPop" Visibility="Collapsed">
<StackPanel Orientation="Horizontal">
<SearchBox Name="Search" GotFocus="Search_Focus" LostFocus="Search_Focus"/>
<AppBarButton Name="SearchClose" Icon="Cancel" Click="Search_Close" />
</StackPanel>
</FlyoutPresenter>
</Grid>
C#:
public partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
// Close app bar, show search box, and set margin to bottom of page
private void Search_Click(object sender, RoutedEventArgs e)
{
BAppBar.IsOpen = false;
SearchPop.Visibility = Windows.UI.Xaml.Visibility.Visible;
SearchPop.Margin = new Thickness(0, MainGrid.ActualHeight - SearchPop.ActualHeight, 0, 0);
}
// Set margin for opening/closing virtual keyboard
private void Search_Focus(object sender, RoutedEventArgs e)
{
Windows.UI.ViewManagement.InputPane.GetForCurrentView().Showing += (s, args) =>
{
double flyoutOffset = (int)args.OccludedRect.Height - SearchPop.ActualHeight;
SearchPop.Margin = new Thickness(0, flyoutOffset, 0, 0);
};
Windows.UI.ViewManagement.InputPane.GetForCurrentView().Hiding += (s, args) =>
{
SearchPop.Margin = new Thickness(0, MainGrid.ActualHeight - SearchPop.ActualHeight, 0, 0);
};
}
// Close search
private void Search_Close(object sender, RoutedEventArgs e)
{
SearchPop.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
}
What I need is for the box to not be affected by the user scrolling in the screen. In HTML, this is called Fixed Positioning. I have read that it is not natively possible in XAML, but that there are workarounds. I have read these MSDN and SO links, but they didn't really help:
http://social.msdn.microsoft.com/Forums/en-US/9779328a-a7cd-447d-a4ac-bcc952083f43/fixed-positioning-in-wpf?forum=wpf
http://social.msdn.microsoft.com/Forums/windowsapps/en-US/7349d01d-dc0e-4e1c-9327-df90e00fbebf/how-to-handle-the-appearance-of-the-onscreen-keyboard?forum=winappswithcsharp
Popup control moves with parent
You can simulate the fixed behavior in XAML in a very simple way:
<Grid Background="White" x:Name="MainGrid">
<ContentControl VerticalAligment="Stretch" HorizontalAligment="Stretch">
<!--All other visual controls, the float item will be located over all controls located here, even scrolls viewers-->
</ContentControl>
<!-- Float item -->
<SomeControl>
<!--The control you want be over in the fixed position,
you can set the layout to it, and locate it where you want
just set the Vertical/Horizontal Aligment, margin, height, width-->
</SomeControl>
</Grid>
(Sorry if code sample has some sintax errors, I had write it in the fly)
Also wpf has some controls that are displayed on a layer over all other, this elements are context menus, tooltips and adorners, you also could try them.
I hope this ideas helps.
I have a Canvas with a TextBlock like so:
<Canvas x:Name="ContentPanel" Grid.Row="1" DoubleTapped="ContentPanel_DoubleTapped">
<TextBlock x:Name="WordBlock" FontSize="226.667" FontFamily="Segoe UI Semilight" TextAlignment="Center"
RenderTransformOrigin="0.5, 0.5">
<TextBlock.RenderTransform>
<TranslateTransform x:Name="translate"/>
</TextBlock.RenderTransform>
</TextBlock>
</Canvas>
My app is such that when the user navigates to this page, the TextBlock will be centered in the Canvas and if the TextBlock's width is greater than that of the canvas, the marquee animation will occur:
private void SetAnimation()
{
Canvas.SetLeft(WordBlock, (ContentPanel.ActualWidth - WordBlock.ActualWidth) / 2);
Canvas.SetTop(WordBlock, (ContentPanel.ActualHeight - WordBlock.ActualHeight) / 2);
if (WordBlock.ActualWidth > ContentPanel.ActualWidth)
{
MarqueeAnimation.From = WordBlock.ActualWidth;
MarqueeAnimation.To = -WordBlock.ActualWidth;
MarqueeAnimation.Duration = new Duration(new TimeSpan(0, 0, 10));
MarqueeBoard.Begin();
}
}
This method is called OnNavigatedTo. I can't figure out why the TextBlock won't center because the ActualHeight and ActualWidth properties are always coming back as 0.0. I don't want to put fixed sizes because this is a Windows Store app and would like for it to be scalable across different screen sizes.
Any ideas? I'm stuck.
When OnNavigatedTo is called I don't believe the page has actually been drawn yet. I had similar confusion when trying to resize and rearrange things. The answer appeared to be to wait until the page has loaded and do the calculation then:
public ChallengePage()
{
this.InitializeComponent();
this.Loaded += ChallengePage_Loaded;
}
void ChallengePage_Loaded(object sender, RoutedEventArgs e)
{
*Do your calculations which use ActualWidth and ActualHeight in here*
}
You'll need something like that I believe. Adding the Loaded handler into your initialize for the page, and then you can call SetAnimation from in there.