please look at this:
<Grid>
<ScrollViewer>
<Grid Background="Red" Width="50" Height="50" VerticalAlignment="Top" Margin="0,-50,0,0"/>
</ScrollViewer>
</Grid>
here the red grid is not visible because of its margin. but when user pulls down, it will be visible on the screen.
How can I know when it is visible? thanks.
(It's a WP8 app, if that matters)
This method might come handy for you.
private bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
if (!element.IsVisible)
return false;
Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}
Related
I am using WPF Live-Charts (https://lvcharts.net)
I want the tooltip to display the point value according to the mouse cursor movement, as in the image link below.
I tried, but I haven't found a way to display the tooltip without hovering the mouse cursor over the point in Live-Charts.
Examples:
If anyone has done this, can you give some advice?
The solution is relatively simple. The problem with LiveCharts is, that it not well documented. It gets you easily started by providing some examples that target general requirements. But for advanced scenarios, the default controls doesn't offer enough flexibility to customize the behavior or layout. There is no documentation about the details on how things work or what the classes of the library are intended for.
Once I checked the implementation details, I found the controls to be really horrible authored or designed.
Anyway, this simple feature you are requesting is a good example for the shortcomings of the library - extensibility is really bad. Even customization is bad. I wish the authors would have allowed templates, as this would make customization a lot easier. It should be simple to to extend the existing behavior, but apparently its not, unless you know about undocumented implementation details.
The library doesn't come in like a true WPF library. I don't know the history, maybe it's a WinForms port by WinForms devs.
But it's free and open source. And that's a big plus.
The following example draws a cursor on the plotting area which snaps to the nearest chart point and higlights it, while the mouse is moving.
A custom ToolTip follows the mouse pointer to show info about the currently selected chart point:
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
var chartValues = new ChartValues<Point>();
// Create a sine
for (double x = 0; x < 361; x++)
{
var point = new Point() {X = x, Y = Math.Sin(x * Math.PI / 180)};
chartValues.Add(point);
}
SeriesCollection = new SeriesCollection
{
new LineSeries
{
Configuration = new CartesianMapper<Point>()
.X(point => point.X)
.Y(point => point.Y),
Title = "Series X",
Values = chartValues,
Fill = Brushes.DarkRed
}
};
}
private ChartPoint selectedChartPoint;
public ChartPoint SelectedChartPoint
{
get => this.selectedChartPoint;
set
{
this.selectedChartPoint = value;
OnPropertyChanged();
}
}
private double cursorScreenPosition;
public double CursorScreenPosition
{
get => this.cursorScreenPosition;
set
{
this.cursorScreenPosition = value;
OnPropertyChanged();
}
}
public SeriesCollection SeriesCollection { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void MoveChartCursorAndToolTip_OnMouseMove(object sender, MouseEventArgs e)
{
var chart = sender as CartesianChart;
if (!TryFindVisualChildElement(chart, out Canvas outerCanvas) ||
!TryFindVisualChildElement(outerCanvas, out Canvas graphPlottingArea))
{
return;
}
var viewModel = this.DataContext as ViewModel;
Point chartMousePosition = e.GetPosition(chart);
// Remove visual hover feedback for previous point
viewModel.SelectedChartPoint?.View.OnHoverLeave(viewModel.SelectedChartPoint);
// Find current selected chart point for the first x-axis
Point chartPoint = chart.ConvertToChartValues(chartMousePosition);
viewModel.SelectedChartPoint = chart.Series[0].ClosestPointTo(chartPoint.X, AxisOrientation.X);
// Show visual hover feedback for previous point
viewModel.SelectedChartPoint.View.OnHover(viewModel.SelectedChartPoint);
// Add the cursor for the x-axis.
// Since Chart internally reverses the screen coordinates
// to match chart's coordinate system
// and this coordinate system orientation applies also to Chart.VisualElements,
// the UIElements like Popup and Line are added directly to the plotting canvas.
if (chart.TryFindResource("CursorX") is Line cursorX
&& !graphPlottingArea.Children.Contains(cursorX))
{
graphPlottingArea.Children.Add(cursorX);
}
if (!(chart.TryFindResource("CursorXToolTip") is FrameworkElement cursorXToolTip))
{
return;
}
// Add the cursor for the x-axis.
// Since Chart internally reverses the screen coordinates
// to match chart's coordinate system
// and this coordinate system orientation applies also to Chart.VisualElements,
// the UIElements like Popup and Line are added directly to the plotting canvas.
if (!graphPlottingArea.Children.Contains(cursorXToolTip))
{
graphPlottingArea.Children.Add(cursorXToolTip);
}
// Position the ToolTip
Point canvasMousePosition = e.GetPosition(graphPlottingArea);
Canvas.SetLeft(cursorXToolTip, canvasMousePosition.X - cursorXToolTip.ActualWidth);
Canvas.SetTop(cursorXToolTip, canvasMousePosition.Y);
}
// Helper method to traverse the visual tree of an element
private bool TryFindVisualChildElement<TChild>(DependencyObject parent, out TChild resultElement)
where TChild : DependencyObject
{
resultElement = null;
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is Popup popup)
{
childElement = popup.Child;
}
if (childElement is TChild)
{
resultElement = childElement as TChild;
return true;
}
if (TryFindVisualChildElement(childElement, out resultElement))
{
return true;
}
}
return false;
}
}
MainWindow.xaml
<Window>
<Window.DataComtext>
<ViewModel />
</Window.DataContext>
<CartesianChart MouseMove="MoveChartCursorAndToolTip_OnMouseMove"
Series="{Binding SeriesCollection}"
Zoom="X"
Height="600">
<CartesianChart.Resources>
<!-- The cursor for the x-axis that snaps to the nearest chart point -->
<Line x:Key="CursorX"
Canvas.ZIndex="2"
Canvas.Left="{Binding SelectedChartPoint.ChartLocation.X}"
Y1="0"
Y2="{Binding ElementName=CartesianChart, Path=ActualHeight}"
Stroke="Gray"
StrokeThickness="1" />
<!-- The ToolTip that follows the mouse pointer-->
<Border x:Key="CursorXToolTip"
Canvas.ZIndex="3"
Background="LightGray"
Padding="8"
CornerRadius="8">
<StackPanel Background="LightGray">
<StackPanel Orientation="Horizontal">
<Path Height="20" Width="20"
Stretch="UniformToFill"
Data="{Binding SelectedChartPoint.SeriesView.(Series.PointGeometry)}"
Fill="{Binding SelectedChartPoint.SeriesView.(Series.Fill)}"
Stroke="{Binding SelectedChartPoint.SeriesView.(Series.Stroke)}"
StrokeThickness="{Binding SelectedChartPoint.SeriesView.(Series.StrokeThickness)}" />
<TextBlock Text="{Binding SelectedChartPoint.SeriesView.(Series.Title)}"
VerticalAlignment="Center" />
</StackPanel>
<TextBlock Text="{Binding SelectedChartPoint.X, StringFormat=X:{0}}" />
<TextBlock Text="{Binding SelectedChartPoint.Y, StringFormat=Y:{0}}" />
</StackPanel>
</Border>
</CartesianChart.Resources>
<CartesianChart.AxisY>
<Axis Title="Y" />
</CartesianChart.AxisY>
<CartesianChart.AxisX>
<Axis Title="X" />
</CartesianChart.AxisX>
</CartesianChart>
<Window>
I have this DataGrid and this Canvas:
<DataGrid Canvas.ZIndex="1" x:Name="dgTimeline"/>
<Canvas Height="30" Width="999" Canvas.ZIndex="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="71,387,0,0">
<Line Name="time" X1="0" Y1="0" X2="0" Y2="24" Stroke="Black" StrokeThickness="2"/>
</Canvas>
Which results in:
However, when I move the horizontal scroll bar of the DataGrid the Canvas obviously stays on its position because its parent is the Window and not the DataGrid:
Is it possible to keep Canvas' position relative to the DataGrid instead of its parent in such a way that when scrolling the DataGrid the Canvas would stay stationary as it was a DataGrid's element? I tried to put the Canvas inside of the DataGrid but that didn't work.
You can add horizontal scrollbar to canvas and then try to synchronize the horizontal scrolls of canvas and datagrid. something like ...
private void dataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
canvasScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
}
private void canvasScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ScrollViewer dgScrollViewer = GetScrollViewerInstance();
dgScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
}
private ScrollViewer GetScrollViewerInstance()
{
var ctrl = VisualTreeHelper.GetChild(dataGrid, 0);
if (ctrl is Border)
{
var ctrl1 = VisualTreeHelper.GetChild(ctrl, 0);
if (ctrl1 is ScrollViewer)
{
dgScrollViewer = ctrl1 as ScrollViewer;
}
}
}
This code is just to give you an idea of how to do it and not a actual working code. You set the HorizontalScrollBarVisibility for Canvas to Hidden if you don't want to show it. You will not need the second event handler in that case.
I have created my custom adorner to cover my main window with a gray canvas alongwith a textblock at center to show some status text while i was working on other window.
What i am currently doing is fetching the required adornerElement(ie Canvas with a textblock) from my resources and passing it to an adorner in my view constructor like this -
ResourceDictionary reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
UIElement adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as UIElement;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
But i want to update that text in textblock in some scenarios say if i click on some button in other window but how to update the text dynamically??
Adorner element from Resource file-
<Grid x:Key="RefreshingReportAdorner">
<Rectangle Fill="Gray"
StrokeThickness="1"
Stroke="Gray"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<Border BorderBrush="Black"
BorderThickness="2"
Background="White"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock i18n:LanguageManager.VisualId="6"
Text="Some Text(Update dynamically)"
Padding="15,10,15,10"/>
</Border>
</Grid>
Let me know if additional code or approach required..
Have you tried to create some model and push it to RefreshingReportAdorner element's DataContext?
Code:
var reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
var adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as FrameworkElement;
var model = new Model();
model.MyText = "Initial text";
adornerElement.DataContext = model;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
...
model.MyText = "Text after click";
XAML:
<TextBlock i18n:LanguageManager.VisualId="6"
Text="{Binding MyText}"
Padding="15,10,15,10"/>
Model:
public class Item : INotifyPropertyChanged
{
private string _myText;
public string MyText
{
get
{
return this._myText;
}
set
{
this._myText= value;
this.OnPropertyChanged("MyText");
}
}
}
I have a documentviewer which i used in my wpf project to show xps document reports of having around 600 pages which is working great. But from user point of view i like to show the current page number as a tooltip on my scrollviewer while dragging the scroll stating the current page number in view. Somewhat like in a PDF file like this -
I was looking out for some ideas how to implement this. Just a current page number if not possible to show a thumbnail image would be good enough for me.
Is there any in-built support in documentviewer for this functionality??
Thanks for any help..
I cannot find anything like IsScrolling so i would approach it like this:
<Popup Name="docPopup" AllowsTransparency="True" PlacementTarget="{x:Reference docViewer}" Placement="Center">
<Border Background="Black" CornerRadius="5" Padding="10" BorderBrush="White" BorderThickness="1">
<TextBlock Foreground="White">
<Run Text="{Binding ElementName=docViewer, Path=MasterPageNumber, Mode=OneWay}"/>
<Run Text=" / "/>
<Run Text="{Binding ElementName=docViewer, Path=PageCount, Mode=OneWay}"/>
</TextBlock>
</Border>
</Popup>
<DocumentViewer Name="docViewer" ScrollViewer.ScrollChanged="docViewer_ScrollChanged"/>
The popup should be displayed when the document is scrolled, then it should fade out after some time. This is done in the handler:
DoubleAnimationUsingKeyFrames anim;
private void docViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (anim == null)
{
anim = new DoubleAnimationUsingKeyFrames();
anim.Duration = (Duration)TimeSpan.FromSeconds(1);
anim.KeyFrames.Add(new DiscreteDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0))));
anim.KeyFrames.Add(new DiscreteDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.5))));
anim.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1))));
}
anim.Completed -= anim_Completed;
docPopup.Child.BeginAnimation(UIElement.OpacityProperty, null);
docPopup.Child.Opacity = 1;
docPopup.IsOpen = true;
anim.Completed += anim_Completed;
docPopup.Child.BeginAnimation(UIElement.OpacityProperty, anim);
}
void anim_Completed(object sender, EventArgs e)
{
docPopup.IsOpen = false;
}
Edit: The event fires also on scrolls done via mouse-wheel etc. you could wrap everything in the handler in if (Mouse.LeftButton == MouseButtonState.Pressed), not 100% accurate but who scrolls with the MouseWheel while left-clicking?
I creating a control for WPF, and I have a question for you WPF gurus out there.
I want my control to be able to expand to fit a resizable window.
In my control, I have a list box that I want to expand with the window. I also have other controls around the list box (buttons, text, etc).
I want to be able to set a minimum size on my control, but I want the window to be able to be sized smaller by creating scroll bars for viewing the control.
This creates nested scroll areas: One for the list box and a ScrollViewer wrapping the whole control.
Now, if the list box is set to auto size, it will never have a scroll bar because it is always drawn full size within the ScrollViewer.
I only want the control to scroll if the content can't get any smaller, otherwise I don't want to scroll the control; instead I want to scroll the list box inside the control.
How can I alter the default behavior of the ScrollViewer class? I tried inheriting from the ScrollViewer class and overriding the MeasureOverride and ArrangeOverride classes, but I couldn't figure out how to measure and arrange the child properly. It appears that the arrange has to affect the ScrollContentPresenter somehow, not the actual content child.
Any help/suggestions would be much appreciated.
I've created a class to work around this problem:
public class RestrictDesiredSize : Decorator
{
Size lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
protected override Size MeasureOverride(Size constraint)
{
Debug.WriteLine("Measure: " + constraint);
base.MeasureOverride(new Size(Math.Min(lastArrangeSize.Width, constraint.Width),
Math.Min(lastArrangeSize.Height, constraint.Height)));
return new Size(0, 0);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
Debug.WriteLine("Arrange: " + arrangeSize);
if (lastArrangeSize != arrangeSize) {
lastArrangeSize = arrangeSize;
base.MeasureOverride(arrangeSize);
}
return base.ArrangeOverride(arrangeSize);
}
}
It will always return a desired size of (0,0), even if the containing element wants to be bigger.
Usage:
<local:RestrictDesiredSize MinWidth="200" MinHeight="200">
<ListBox />
</local>
You problem arises, because Controls within a ScrollViewer have virtually unlimited space available. Therefore your inner ListBox thinks it can avoid scrolling by taking up the complete height necessary to display all its elements. Of course in your case that behaviour has the unwanted side effect of exercising the outer ScrollViewer too much.
The objective therefore is to get the ListBox to use the visible height within the ScrollViewer iff there is enough of it and a certain minimal height otherwise. To achieve this, the most direct way is to inherit from ScrollViewer and override MeasureOverride() to pass an appropriately sized availableSize (that is the given availableSize blown up to the minimal size instead of the "usual" infinity) to the Visuals found by using VisualChildrenCount and GetVisualChild(int).
I used Daniels solution. That works great. Thank you.
Then I added two boolean dependency properties to the decorator class: KeepWidth and KeepHeight. So the new feature can be suppressed for one dimension.
This requires a change in MeasureOverride:
protected override Size MeasureOverride(Size constraint)
{
var innerWidth = Math.Min(this._lastArrangeSize.Width, constraint.Width);
var innerHeight = Math.Min(this._lastArrangeSize.Height, constraint.Height);
base.MeasureOverride(new Size(innerWidth, innerHeight));
var outerWidth = KeepWidth ? Child.DesiredSize.Width : 0;
var outerHeight = KeepHeight ? Child.DesiredSize.Height : 0;
return new Size(outerWidth, outerHeight);
}
While I wouldn't recommend creating a UI that requires outer scroll bars you can accomplish this pretty easily:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListBox Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" MinWidth="200"/>
<Button Grid.Row="0" Grid.Column="1" Content="Button1"/>
<Button Grid.Row="1" Grid.Column="1" Content="Button2"/>
<Button Grid.Row="2" Grid.Column="1" Content="Button3"/>
</Grid>
</ScrollViewer>
</Window>
I don't really recommend this. WPF provides exceptional layout systems, like Grid, and you should try to allow the app to resize itself as needed. Perhaps you can set a MinWidth/MinHeight on the window itself to prevent this resizing?
Create a method in the code-behind that sets the ListBox's MaxHeight to the height of whatever control is containing it and other controls. If the Listbox has any controls/margins/padding above or below it, subtract their heights from the container height assigned to MaxHeight. Call this method in the main windows "loaded" and "window resize" event handlers.
This should give you the best of both worlds. You are giving the ListBox a "fixed" size that will cause it to scroll in spite of the fact that the main window has its own scrollbar.
for 2 ScrollViewer
public class ScrollExt: ScrollViewer
{
Size lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
public ScrollExt()
{
}
protected override Size MeasureOverride(Size constraint)
{
base.MeasureOverride(new Size(Math.Min(lastArrangeSize.Width, constraint.Width),
Math.Min(lastArrangeSize.Height, constraint.Height)));
return new Size(0, 0);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
if (lastArrangeSize != arrangeSize)
{
lastArrangeSize = arrangeSize;
base.MeasureOverride(arrangeSize);
}
return base.ArrangeOverride(arrangeSize);
}
}
code:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Background="Beige" Width="600" Text="Example"/>
<Grid Grid.Column="1" x:Name="grid">
<Grid Grid.Column="1" Margin="25" Background="Green">
<local:ScrollExt HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid Width="10000" Margin="25" Background="Red" />
</local:ScrollExt>
</Grid>
</Grid>
</Grid>
</ScrollViewer>
I ended up combining Daniels answer and Heiner's answer. I decided to post the entire solution to make it easier for people to adopt this if needed. Here's my decorator class:
public class RestrictDesiredSizeDecorator : Decorator
{
public static readonly DependencyProperty KeepWidth;
public static readonly DependencyProperty KeepHeight;
#region Dependency property setters and getters
public static void SetKeepWidth(UIElement element, bool value)
{
element.SetValue(KeepWidth, value);
}
public static bool GetKeepWidth(UIElement element)
{
return (bool)element.GetValue(KeepWidth);
}
public static void SetKeepHeight(UIElement element, bool value)
{
element.SetValue(KeepHeight, value);
}
public static bool GetKeepHeight(UIElement element)
{
return (bool)element.GetValue(KeepHeight);
}
#endregion
private Size _lastArrangeSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
static RestrictDesiredSizeDecorator()
{
KeepWidth = DependencyProperty.RegisterAttached(
nameof(KeepWidth),
typeof(bool),
typeof(RestrictDesiredSizeDecorator));
KeepHeight = DependencyProperty.RegisterAttached(
nameof(KeepHeight),
typeof(bool),
typeof(RestrictDesiredSizeDecorator));
}
protected override Size MeasureOverride(Size constraint)
{
Debug.WriteLine("Measure: " + constraint);
var keepWidth = GetValue(KeepWidth) as bool? ?? false;
var keepHeight = GetValue(KeepHeight) as bool? ?? false;
var innerWidth = keepWidth ? constraint.Width : Math.Min(this._lastArrangeSize.Width, constraint.Width);
var innerHeight = keepHeight ? constraint.Height : Math.Min(this._lastArrangeSize.Height, constraint.Height);
base.MeasureOverride(new Size(innerWidth, innerHeight));
var outerWidth = keepWidth ? Child.DesiredSize.Width : 0;
var outerHeight = keepHeight ? Child.DesiredSize.Height : 0;
return new Size(outerWidth, outerHeight);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
Debug.WriteLine("Arrange: " + arrangeSize);
if (_lastArrangeSize != arrangeSize)
{
_lastArrangeSize = arrangeSize;
base.MeasureOverride(arrangeSize);
}
return base.ArrangeOverride(arrangeSize);
}
}
and here's how I use it in the xaml:
<ScrollViewer>
<StackPanel Orientation="Vertical">
<Whatever />
<decorators:RestrictDesiredSizeDecorator MinWidth="100" KeepHeight="True">
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Height="Auto"
MaxHeight="360"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="WrapWithOverflow"
/>
</decorators:RestrictDesiredSizeDecorator>
<Whatever />
</StackPanel>
</ScrollViewer
The above creates a textbox that will grow vertically (until it hits MaxHeight) but will match the parent's width without growing the outer ScrollViewer. Resizing the window/ScrollViewer to less than 100 wide will force the outer ScrollViewer to show the horizontal scroll bars. Other controls with inner ScrollViewers can be used as well, including complex grids.