Is there a way to visually indicate that there is scrollable content below the visible area of a <ScrollViewer>?
I see this effect in applications like Microsoft Teams. A shadow appears at the bottom of the scrollable area to indicate that more content is present.
None of the properties of <ScrollViewer> seem to be a clear match. But I'm hoping I can avoid having to programmatically show/hide an element below the <ScrollViewer> based on the scroll position.
I have to say that currently there is no built-in API in ScrollViewer that could directly show a shadow there if the content is not ended.
You might still need to check it programmatically by handling the ViewChanged event of the ScrollViewer and add a custom element.
You could use the following code to check if the ScrollViewer reaches the end:
<Grid>
<ScrollViewer Name="MyScrollViewer" ViewChanged="MyScrollViewer_ViewChanged">
<Rectangle
x:Name="MyRectangle"
Width="3000"
Height="3000"
Fill="Blue"
Margin="20" />
</ScrollViewer>
</Grid>
Code behind:
private void MyScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
var verticalOffset = MyScrollViewer.VerticalOffset;
var maxVerticalOffset = MyScrollViewer.ScrollableHeight;
if (maxVerticalOffset < 0 || verticalOffset == maxVerticalOffset)
{
// content reaches the end
MyRectangle.Fill = new SolidColorBrush(Colors.Red);
}
else
{
// content does not reach the end
MyRectangle.Fill = new SolidColorBrush(Colors.Blue);
}
}
Related
I am making an app in WPF that displays an image which can be dragged and zoomed. Bottom, right and upper sides contain some UI elements like buttons and in the center I have a TabControl to which I add TabItems in the code of ViewModel. These TabItems consists of their content (an image) and a header where I have tab buttons. The problem I have is that an image I drag covers the header but not the buttons as you can see on the screenshot. The behavior I expect is to have this image hidden underneath the entire header, not only buttons. It only happens with the bottom side. When I drag the image to the top or right it gets hidden behind the sides like it's supposed to.
Header issue
I tried to change its background, opacity and ZIndex but nothing worked for me.
Here is my code.
XAML:
<TabControl Grid.Row="1" Grid.Column="0" TabStripPlacement="Bottom" Background="LightGray" ItemsSource="{Binding LayoutTabs}"
SelectedIndex="0" SelectedItem="{Binding SelectedTab, Mode=OneWayToSource}"/>
C#:
LayoutTabs = new BindableCollection<TabItem>();
for (int i = 0; i < _content.LayoutImages.Count; i++)
{
DrawingImage drawing = _content.LayoutImages.ElementAt(i);
Image image = new Image() { Source = drawing };
image.MouseMove += OnMouseMove;
var container = new LayoutContainer()
{
Background = Brushes.WhiteSmoke,
Child = image,
Focusable = true,
};
var tabItem = new TabItem()
{
Header = _content.GetLayoutName(i),
Content = container
};
LayoutTabs.Add(tabItem);
}
That behaviour is due to the headerpanels background being set to transparent in the default control template. If you right click the tabcontrol in the Designer window (not xaml editor) and click on Edit Template->Edit a Copy you get a copy of that tempalte and can then modify the headerpanel with your BackgroundColor and if need be increase the Zindex:
[....]
//this is the line to make your changes on:
<TabPanel x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="true" Margin="2,2,2,0" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1"/>
[....]
I am creating an application which currently lets users scroll to zoom into an image. Currently, if I try to use a scrollview to allow users to scroll down the page, it breaks this feature. Is there any way for me to implement a scrollbar that does not access the scroll wheel? What I am looking for is a way to only allow users to access the scrollbar by clicking it, not by using the scrollwheel.
Set the MouseWheel event on your image and set e.Handled = true at the end.
Contrived example -
XAML:
<Grid>
<ScrollViewer>
<Canvas Width="1000" Height="1000" Background="PeachPuff" MouseWheel="Canvas_MouseWheel" />
</ScrollViewer>
</Grid>
Code:
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
(sender as Canvas).Background = Brushes.Blue;
e.Handled = true;
}
I have a GridView control bound to a collection of objects having a BitmapImage property. GridView item template's Image control has a fixed size, while actual pictures may vary in size, can be smaller or bigger than the Image. So i use Stretch=Uniform for bigger pictures, and Stretch=None for smaller. I set the Stretch property on Image_Loaded event:
private void img_ImageOpened(object sender, RoutedEventArgs e)
{
var img = sender as Image;
if (img.Width > (img.Source as BitmapImage).PixelWidth)
{
img.Stretch = Stretch.None;
}
else
{
img.Stretch = Stretch.Uniform;
}
}
So the pics are fitted pretty well:
But if i clear the bound collection and fill it again, things get really messy:
I have spent pretty much time trying to resolve this issue. Image_Loaded isn't called for the second time, so i thought it's something with item caching. I have tried to set CacheMode to null, but that didn't help. Tried to handle various events but with no success either.
Please help!
Thanks
Download my project - i have removed anything not related to the problem, there are only 90 lines of code.
PS i have found the right event to subscribe, it's Image_DataContextChanges. It seems GridView items are reused, and on updates objects and particular grid items can be confused. Image_Loaded isn't called, so an object gets into a random grid item with arbitrary stretching. DataContextChanges fires each time instead so it can be used to change stretching method on the fly.
And while it works i think the Clemens' solution below is just better. Will use it next time.
Instead of adjusting the Image's Stretch property in code behind, you can put the Image control in a Viewbox, which in addition to Stretch also has a StretchDirection property. If you set that to DownOnly, the images will only be stretched to smaller sizes.
<DataTemplate x:DataType="local:Thing">
<Border BorderBrush="Black" BorderThickness="1" Background="Black">
<Viewbox Width="48" Height="48" Stretch="Uniform" StretchDirection="DownOnly">
<Image Stretch="None" Source="{x:Bind Image}" />
</Viewbox>
</Border>
</DataTemplate>
I fixed your code by making sure that you clean up the Stuff collection entirely when hiding the grid
Stuff = null
This means that in the Show_Click handler, you reinitialize the collection.
Stuff = new ObservableCollection<Thing>();
If you will continue to use bindings, you'll need to raise a PropertyChanged notification when you do that (recommended). If you don't want to use bindings, just re-set the ItemsSource to the new instance of Stuff (see below).
private void Show_Click(object sender, RoutedEventArgs e)
{
Stuff = new ObservableCollection<Thing>();
foreach (var s in source.Except(Stuff)) Stuff.Add(s);
gv.ItemsSource = Stuff;
}
private void Hide_Click(object sender, RoutedEventArgs e)
{
Stuff.Clear();
Stuff = null;
}
I am currently working on my first WPF project and trying to make a ListView scrollable.
At first I thought this could be easily done by simply limiting the ListView's width and height and thus forcing a scrollbar to appear automatically whenever the content exceeds its space. This seemed fine at first but due to the handled PreviewMouseDown event (which enables dragging the list's items) it doesn't work after selecting an item.
Second attempt (using ScrollViewer)
<ScrollViewer>
<ListView ItemsSource="{Binding FileViewModels}"
PreviewMouseDown="ListView_MouseMove"
Height="450" Width="200"/>
</ScrollViewer>
Of course, this resulted in a second scrollbar whenever the list's content became larger than its max height. And dragging the bar still didn't work after selecting an item.
Third (quite foolish) attempt (disabling scrollbar duplicate)
<ScrollViewer>
<ListView ItemsSource="{Binding FileViewModels}"
PreviewMouseDown="ListView_MouseMove"
Height="450" Width="200"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</ScrollViewer>
This removed the scrollbar duplicate and enabled scrolling via mouse wheel but disabled the scrollbar, so you couldn't move by clicking and dragging it.
Fourth attempt (constant size of the ScrollViewer)
<ScrollViewer Height="450" Width="200">
<ListView ItemsSource="{Binding FileViewModels}"
PreviewMouseDown="ListView_MouseMove"/>
</ScrollViewer>
Removed the width/height constraint from the ListView and moved it to the ScrollViewer. This enables the scrollbar and removes the duplicate. Unfortunately the mouse wheel doesn't work anymore (dragging the scroll bar works fine).
Could somebody please explain to me why the mouse wheel doesn't work anymore and how to fix this?
Edit
Maybe I should go back to my first solution.
Obviously, the ListView's template already contains a ScrollViewer. The remaining problem would then be that I cannot drag the scrollbar after selecting an item because of the handled PreviewMouseDown event (scrolling via MouseWheel still works in that case). Should I handle the dragging of items differently (it worked fine for me, before wanting to add a scrollbar)? Or is there a way to detect if the cursor is above the scrollbar (so I could then deselect the item which enables scrolling)?
Or are there any other suggestions?
This may help you..
private void ListViewScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
ScrollViewer scv = (ScrollViewer)sender;
scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
e.Handled = true;
}
This would probably be the most comfortable solution:
<ListView.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter></ItemsPresenter>
</ScrollViewer>
</ControlTemplate>
</ListView.Template>
For me this worked:
<ListView.Template>
<ControlTemplate>
<!-- Empty template to allow ScrollViewer to capture mouse scroll -->
<ItemsPresenter />
</ControlTemplate>
</ListView.Template>
instead of this:
<ListView.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter></ItemsPresenter>
</ScrollViewer>
</ControlTemplate>
</ListView.Template>
<ScrollViewer Background="Transparent">
If Background is null, the mouse wheel will not work on ScrollViewer. You can set the Background to Transparent or some other value.
In my case this helped:
<ScrollViewer ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Auto" >
<DataGrid x:Name="dataGrid" SelectionMode="Single" ItemsSource="{Binding}" SelectedValuePath="{Binding Item}" AutoGenerateColumns="True">
</DataGrid>
</ScrollViewer>
The design was disabling VerticalScrollBarVisibility attribute in outer scope , i.e. in ScrollViewer.
I want to add some comment to the solution Rocky provided. It worked fine for me, but later I needed to use it in a different window to scroll Grid. I faced a problem: the ScrollViewer did not scroll to the bottom end. The reason was because of attempts to set the invalid VerticalOffset value. The code below works fine for me (just need to change PreviewMouseWheel handler:
private void UIElement_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scroll = (ScrollViewer)sender;
if (e.Delta < 0)
{
if (scroll.VerticalOffset - e.Delta <= scroll.ExtentHeight - scroll.ViewportHeight)
{
scroll.ScrollToVerticalOffset(scroll.VerticalOffset - e.Delta);
}
else
{
scroll.ScrollToBottom();
}
}
else
{
if (scroll.VerticalOffset + e.Delta > 0)
{
scroll.ScrollToVerticalOffset(scroll.VerticalOffset - e.Delta);
}
else
{
scroll.ScrollToTop();
}
}
e.Handled = true;
}
I have a Page like in the Xaml below which i want to use like a ModalDialog.
The Problem is that when I Pop the Dialog up, that the Opacity of the second Grid which holds the Content is not changed back to 100% and I see from the Page where it is Popuped the underlying controls. For more Detail see the Screenshot.
Is there a way that I can change back the Opacity of the second Grid to 100% that no control behind it can see through?
For completneness I have added the Code which i'm using to bring up the Popup.
ModalDialog Xaml:
<Page>
<Grid x:Name="RootPanel" Background="{StaticResource LucentBlue}" Opacity=".75">
<Border >
<Grid VerticalAlignment="Center"
Height="300" Background="{StaticResource PremiumBlue}" Opacity="1">
</Grid>
</Border>
</Grid>
</Page>
Code Behind Hosted Page:
private Popup _saveDialog;
private void SaveSettingsCommandLogic(object obj)
{
ModalDialog dlg = new ModalDialog();
dlg.CloseRequested += DlgOnCloseRequested;
_saveDialog = new Popup();
_saveDialog.Child = dlg;
_saveDialog.IsOpen = true;
}
Here's a solution in metro :
Please remove the Opacity property of both the elements and from the code behind of the ModalDialog class use the following code:
public ModalDialog()
{
this.InitializeComponent();
Color color = Color.FromArgb(150,255,0,0);
RootPanel.Background = new SolidColorBrush(color);
}
The method FromArgb is used to specify the transparency red green and blue values respectively and can rancge from 0-255 .. please test according to ur convinience :)