I have an Oxyplot chart displaying a lineseries, shown as follows
<oxy:Plot x:Name="MyChart"
Title="Real"
Grid.Row="1"
Grid.Column="0">
<oxy:Plot.Series>
<oxy:LineSeries Title="MySeries"/>
</oxy:Plot.Series>
<oxy:Plot.Axes>
<oxy:LinearAxis Position="Left" TicklineColor="White" Title= "MySeries"/>
<oxy:LinearAxis Position="Bottom" TicklineColor="White" />
</oxy:Plot.Axes>
</oxy:Plot>
When the user left clicks on the line, the tracker is displayed, showing the selected data point. I would like to have a handler in my code to get the selected data point but am not sure the correct way to do this.
I have tried adding a handler as follows
this.MyChart.ActualModel.MouseDown += OxyMouseDown;
private void OxyMouseDown(object sender, OxyMouseDownEventArgs e)
{
LineSeries lineSeries = sender as LineSeries;
if (lineSeries != null)
{
double x = lineSeries.InverseTransform(e.Position).X;
}
}
However, although the handler gets called, the sender is never of type LineSeries and therefore I can never transform the point.
Can someone help please?
Thanks.
Replace the Plot element with a PlotView element and create a PlotModel to which you add your series and axes:
PlotModel plotModel = new PlotModel() { Title = "Real" };
LineSeries lineSeries = new LineSeries() { Title = "MySeries" };
plotModel.Series.Add(lineSeries);
//...and add the axes
MyChart.Model = plotModel;
XAML:
<oxy:PlotModel x:Name="MyChart" Grid.Row="1" />
Then you should be able to handle the event of the LineSeries:
lineSeries.MouseDown += OxyMouseDown;
Unfortunately, this solution is know deprecated.
All events except for PlotView where marked as such.
The answer is that the binding of new events should be declared in a PlotController. an exemple is provided here.
to go further, the event can be handled more specifically by this way:
_sectionPlotCtrl.BindMouseDown(OxyMouseButton.Left, new DelegateViewCommand<OxyMouseDownEventArgs>(myPlot_HandleMouseDown));
...
private void myPlot_HandleMouseDown(IView view, IController controller, OxyMouseDownEventArgs e)
{
//handle mouse down stuff
}
the oxyMouseDownEventArgs has a hitTestResult, that would return the nearest point from the serie.
Related
I'm trying to create a snapshot/image/bitmap of an element that I can display as content in another control.
It seems the suggested way to do this is with a VisualBrush, but I can't seem to get it to create a snapshot of the current value and keep that state. When you alter the original source, the changes are applied to all the "copies" that have been made too.
I have made a simple example to show what I mean.
What I want is for the items added to the stackpanel to have the opacity that was set when they were cloned. But instead, changing the opacity on the source changes all "clones".
<StackPanel Width="200" x:Name="sp">
<DockPanel>
<Button Content="Clone"
Click="OnCloneButtonClick" />
<TextBlock Text="Value" x:Name="tb" Background="Red" />
</DockPanel>
</StackPanel>
private void OnCloneButtonClick(object sender, RoutedEventArgs e)
{
tb.Opacity -= 0.1;
var brush = new VisualBrush(tb).CloneCurrentValue();
sp.Children.Add(new Border() { Background = brush, Width = tb.ActualWidth, Height = tb.ActualHeight });
}
I am afraid the visual elements aren't cloned when you call CloneCurrentValue().
You will have to clone the element yourself, for example by serializing the element to XAML and then deserialize it back using the XamlWriter.Save and XamlReader.Parse methods respectively:
private void OnCloneButtonClick(object sender, RoutedEventArgs e)
{
tb.Opacity -= 0.1;
var brush = new VisualBrush(Clone(tb));
sp.Children.Add(new Border() { Background = brush, Width = tb.ActualWidth, Height = tb.ActualHeight });
}
private static Visual Clone(Visual visual)
{
string xaml = XamlWriter.Save(visual);
return (Visual)XamlReader.Parse(xaml);
}
I am searching for a way to create a "Yes to all - Yes - No - No to all" message box. I first tried to use a MessageBox instance but I understood that it is impossible to add custom buttons to it. So here I am with a custom window I created myself which does what I want but its design is far from being at least not ugly.
So my question is: How to reproduce the same layout of a MessageBox like below but with extra buttons?
There is a lot of examples of custom message boxes, both for WPF and WinForms.
I'm very sorry about amount of text, but here is detailed explanation and code examples.
As #mm8 suggested, the easiest way is to create simple Window, build layout for Header (Caption), Icon, Message and Buttons.
XAML:
<Window x:Class="WPFApp.CustomMessageBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFApp"
mc:Ignorable="d"
Title=""
MinHeight="150"
Width="500"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
FontSize="14"
WindowStartupLocation="CenterScreen">
<Grid Margin="5" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<!-- Border for our custom message box -->
<Border Grid.ColumnSpan="3"
Grid.RowSpan="3"
BorderBrush="Gray"
BorderThickness="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Border.Effect>
<DropShadowEffect BlurRadius="4"
ShadowDepth="0"
Direction="270"
Color="Black"
RenderingBias="Performance"/>
</Border.Effect>
</Border>
<!-- Header of our message box to keep Caption and to be used for window move -->
<TextBlock x:Name="CMBCaption"
HorizontalAlignment="Stretch"
Grid.Row="0"
Text="Custom Message Box Caption"
Grid.ColumnSpan="2"
Background="Gainsboro"
Foreground="Black"
FontWeight="SemiBold"
Margin="1,1,1,0"
Padding="5,2.5,0,0"
MouseLeftButtonDown="OnCaptionPress"/>
<!-- Icon for our custom message box -->
<Image x:Name="CMBIcon"
Grid.Column="0"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="36"
Height="36"/>
<!-- TextBlock for message content. Wrapped into Label because of alignment needs -->
<Label Grid.Column="1"
Grid.Row="1"
VerticalContentAlignment="Center"
Margin="2,24,4,24">
<TextBlock x:Name="CMBMessage"
TextWrapping="Wrap"
Text="Custom Message Box Message"/>
</Label>
<!-- Background for button block -->
<Rectangle Grid.Row="2"
Grid.ColumnSpan="2"
Fill="Gainsboro"
Margin="1,0,1,1"/>
<!-- Buttons block -->
<StackPanel x:Name="CMBButtons"
Grid.Row="2"
Grid.ColumnSpan="2"
Orientation="Horizontal"
FlowDirection="RightToLeft"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Margin="0,0,6,0"/>
</Grid>
</Window>
So here is TextBlock ("CMBCaption" for our Caption), Image ("CMBIcon" for our icon), TextBlock ("CMBMessage" for our message, putted into Label as Content property to make correct alignment) and StackPanel ("CMBButtons" for some amount of buttons). "CMB" (if not obvious) is
abbreviation of CustomMessageBox.
That will give you simple little window, which can be movable (by MouseLeftButtonDown="OnCaptionPress" handler on Caption TextBlock), looks simple and fresh, is stretchable (depending on content size) and has StackPanel at bottom to store any amount of buttons you wish.
Code-behind: (check comments and remarks below)
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Brushes = System.Windows.Media.Brushes;
namespace WPFApp
{
public partial class CustomMessageBox : Window
{
// Field that will temporarily store result before we return it and close CustomMessageBox
private static CustomMessageBoxResult result = CustomMessageBoxResult.OK;
// Buttons defined as properties, because couldn't be created (initialized) with event subscription at same time "on-the-fly".
// You can add new different buttons by adding new one as property here
// and to CustomMessageBoxButtons and CustomMessageBoxResult enums
private Button OK
{
get
{
var b = GetDefaultButton();
b.Content = nameof(OK);
b.Click += delegate { result = CustomMessageBoxResult.OK; Close(); };
return b;
}
}
private Button Cancel
{
get
{
var b = GetDefaultButton();
b.Content = nameof(Cancel);
b.Click += delegate { result = CustomMessageBoxResult.Cancel; Close(); };
return b;
}
}
private Button Yes
{
get
{
var b = GetDefaultButton();
b.Content = nameof(Yes);
b.Click += delegate { result = CustomMessageBoxResult.Yes; Close(); };
return b;
}
}
private Button No
{
get
{
var b = GetDefaultButton();
b.Content = nameof(No);
b.Click += delegate { result = CustomMessageBoxResult.No; Close(); };
return b;
}
}
// Add another if you wish
// There is no empty constructor. As least "message" should be passed to this CustomMessageBox
// Also constructor is private to prevent create its instances somewhere and force to use only static Show methods
private CustomMessageBox(string message,
string caption = "",
CustomMessageBoxButtons cmbButtons = CustomMessageBoxButtons.OKOnly,
CustomMessageBoxIcon cmbIcon = CustomMessageBoxIcon.None)
{
InitializeComponent();
// Handle Ctrl+C press to copy message from CustomMessageBox
KeyDown += (sender, args) =>
{
if (Keyboard.IsKeyDown(Key.LeftCtrl) && Keyboard.IsKeyDown(Key.C))
Clipboard.SetText(CMBMessage.Text);
};
// Set message
CMBMessage.Text = message;
// Set caption
CMBCaption.Text = caption;
// Setup Buttons (depending on specified CustomMessageBoxButtons value)
// As StackPanel FlowDirection set as RightToLeft - we should add items in reverse
switch (cmbButtons)
{
case CustomMessageBoxButtons.OKOnly:
_ = CMBButtons.Children.Add(OK);
break;
case CustomMessageBoxButtons.OKCancel:
_ = CMBButtons.Children.Add(Cancel);
_ = CMBButtons.Children.Add(OK);
break;
case CustomMessageBoxButtons.YesNo:
_ = CMBButtons.Children.Add(No);
_ = CMBButtons.Children.Add(Yes);
break;
case CustomMessageBoxButtons.YesNoCancel:
_ = CMBButtons.Children.Add(Cancel);
_ = CMBButtons.Children.Add(No);
_ = CMBButtons.Children.Add(Yes);
break;
// Add another if you wish
default:
_ = CMBButtons.Children.Add(OK);
break;
}
// Set icon (depending on specified CustomMessageBoxIcon value)
// From C# 8.0 could be converted to switch-expression
switch (cmbIcon)
{
case CustomMessageBoxIcon.Information:
CMBIcon.Source = FromSystemIcon(SystemIcons.Information);
break;
case CustomMessageBoxIcon.Warning:
CMBIcon.Source = FromSystemIcon(SystemIcons.Warning);
break;
case CustomMessageBoxIcon.Question:
CMBIcon.Source = FromSystemIcon(SystemIcons.Question);
break;
case CustomMessageBoxIcon.Error:
CMBIcon.Source = FromSystemIcon(SystemIcons.Error);
break;
case CustomMessageBoxIcon.None:
default:
CMBIcon.Source = null;
break;
}
}
// Show methods create new instance of CustomMessageBox window and shows it as Dialog (blocking thread)
// Shows CustomMessageBox with specified message and default "OK" button
public static CustomMessageBoxResult Show(string message)
{
_ = new CustomMessageBox(message).ShowDialog();
return result;
}
// Shows CustomMessageBox with specified message, caption and default "OK" button
public static CustomMessageBoxResult Show(string message, string caption)
{
_ = new CustomMessageBox(message, caption).ShowDialog();
return result;
}
// Shows CustomMessageBox with specified message, caption and button(s)
public static CustomMessageBoxResult Show(string message, string caption, CustomMessageBoxButtons cmbButtons)
{
_ = new CustomMessageBox(message, caption, cmbButtons).ShowDialog();
return result;
}
// Shows CustomMessageBox with specified message, caption, button(s) and icon.
public static CustomMessageBoxResult Show(string message, string caption, CustomMessageBoxButtons cmbButtons, CustomMessageBoxIcon cmbIcon)
{
_ = new CustomMessageBox(message, caption, cmbButtons, cmbIcon).ShowDialog();
return result;
}
// Defines button(s), which should be displayed
public enum CustomMessageBoxButtons
{
// Displays only "OK" button
OKOnly,
// Displays "OK" and "Cancel" buttons
OKCancel,
// Displays "Yes" and "No" buttons
YesNo,
// Displays "Yes", "No" and "Cancel" buttons
YesNoCancel,
// Add another if you wish
}
// Defines icon, which should be displayed
public enum CustomMessageBoxIcon
{
None,
Question,
Information,
Warning,
Error
}
// Defines button, pressed by user as result
public enum CustomMessageBoxResult
{
OK,
Cancel,
Yes,
No
// Add another if you wish
}
// Returns simple Button with pre-defined properties
private static Button GetDefaultButton() => new Button
{
Width = 72,
Height = 28,
Margin = new Thickness(0, 4, 6, 4),
Background = Brushes.White,
BorderBrush = Brushes.DarkGray,
Foreground = Brushes.Black
};
// Converts system icons (like in original message box) to BitmapSource to be able to set it to Source property of Image control
private static BitmapSource FromSystemIcon(Icon icon) =>
Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
// Handler on CustomMessageBox caption-header to allow move window while left button pressed on it
private void OnCaptionPress(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
DragMove();
}
}
}
Remarks.
First of all, to simulate original MessageBox and call it only with CustomMessageBox.Show() (not with new CustomMessageBox().Show() as regular window) you should hide window constructor by making it private.
Enums CustomMessageBoxButtons, CustomMessageBoxIcon and CustomMessageBoxResult are replacements for MessageBoxButton, MessageBoxIcon and MessageBoxResult enums from original MessageBox.
Each Button stored as private property in CustomMessageBox class. On Buttons added to StackPanel on switch statement. They added in order to lay "from right to left" (so first added button will be at most right, next - left of first etc.).
GetDefaultButton method returns, as commented, simple button with pre-defined properties. You can customize it in any way, using gradients, styles, magic - whatever. Even you can remove it and set different style for each Button in its property (in private Button OK, private Button Cancel I mean) to make OK button green, Cancel button red, Yes button pink etc. GetDefaultButton may be rewrited to some kind of common GetButton if you want one method to create any button with specified text and click action as arguments:
private static Button GetButton(string buttonText, RoutedEventHandler clickAction)
{
Button button = new Button
{
Width = 72,
Height = 28
// and other
};
button.Content = buttonText;
button.Click += clickAction;
return button;
}
// In switch statement, when adding buttons to StackPanel, you create and add it instantly
private CustomMessageBox(...)
{
InitializeComponent();
// ...
switch (cmbButtons)
{
case CustomMessageBoxButtons.OKOnly:
_ = CMBButtons.Children.Add(GetButton("OK", delegate
{
result = CustomMessageBoxResult.OK;
Close();
}));
break;
// ...
}
}
private static BitmapSource FromSystemIcon methods, again, as commented, uses default MessageBox icon (or if correct, System Icon) to convert it to BitmapSource. It is needed, because you can't set System Icon to default WPF Image control as Source. If you want to use own icons/images you can remove it and rewrite switch statement where icons set to CMBIcon.Source with paths (URIs) to your own icons/images.
private void OnCaptionPress is handler from TextBlock which stores Caption (or is Header) of CustomMessageBox. Because in XAML WindowStyle property setted to "None" - we can't move window as it is borderless, so this handler uses header to move window until left mouse button pressed on it.
This CustomMessageBox doesn't have Close (x) button. I don't add it because each dialog button has Close() call at click action. You can add if you wish, but this have no sense.
I set window Width property to 500px, so it stretches only by heigth. You also can change it, even make resizable or return WindowStyle to default value (with minimize, maximize and close buttons).
I've also added different "Caption" TextBlock background color in switch statement where icon sets to make MessageBox be better perceived "by eye", but code is huge enough so I removed it from example.
And finally.
To add your Yes to all, No to all or other buttons, on this example, I placed comments // Add another if you wish in places, where you should add new ones:
Add YesToAll, NoToAll or other entries to CustomMessageBoxButtons enum to be able specify it when call, for example, as CustomMessageBox.Show(..., ..., CustomMessageBoxButtons.YesToAllNoToAll).
Add also entries to CustomMessageBoxResult enum to be able return them as result.
Add new Button property, named YesToAll, NoToAll or anyway you need. Set a .Content property with text, that should be displayed on this button ("Yes to all", "No to all" etc.). Add .Click handler, in which set result field with CustomMessageBoxResult enum value of this button and put Close() method to call window close.
And usage is simple, as original MessageBox:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var result1 = CustomMessageBox.Show("Some message");
var result2 = CustomMessageBox.Show("Some message", "My caption");
var result3 = CustomMessageBox.Show("Some message", "My caption", CustomMessageBoxButtons.OKOnly);
var result4 = CustomMessageBox.Show("Some message", "My caption", CustomMessageBoxButtons.OKCancel, CustomMessageBoxIcon.Warning);
// Do whatever with result
}
Few examples:
Only message
Message + Caption
Message + Caption + Buttons
Message + Caption + Buttons + Icon
I specially removed Yes to all, No to all button from example to allow you try create it by yourself. You have, i think, detailed guidance now.
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 been battling this for a couple of days now, and just cannot find the answer. Hoping someone here can help.
We have an animated keyboard that pops up when a user selects a Textblock Control that requires keyboard input. The code that animates the keyboard is fine. But it calls code to adjust the grid that contains the textblock control so that the textblock control always sits just above the animated keyboard. The problem that I am seeing is that when the page that contains the grid is closed, it exceptions with the 'Children' property value in the path '(0).(1)[0].(2)' points to immutable instance of 'System.Windows.Media.TransformCollection' on this line:
_AppWindowControl.IsEnabled = false;
The code that gets called when the keyboard is removed (hidden by "Done" keypress) is this:
/// <summary>
/// Animation to hide keyboard
/// </summary>
private void HideKeyboard()
{
if (_verticalOffset != 0)
{
TranslateTransform tt = new TranslateTransform();
DoubleAnimation slide = new DoubleAnimation(_verticalOffset, 0, TimeSpan.FromMilliseconds(400));
var name = "myTransform" + tt.GetHashCode();
_mainGrid.RegisterName(name, tt);
name = "mySlide" + slide.GetHashCode();
_mainGrid.RegisterName(name, slide);
_mainGrid.RenderTransform = tt;
tt.BeginAnimation(TranslateTransform.YProperty, slide);
_verticalOffset = 0;
}
Storyboard sb = (Storyboard)this.TryFindResource("HideKeyboard");
sb.Completed += new EventHandler(HideKeyboard_Completed);
sb.Begin(this);
}
I added the name registration in hopes that would fix the problem. But it does not. If I remove the assignment _mainGrid.RenderTransform = tt; then the appWindow closes without any error.
Also, I said the problem occurs when closing the keyboard. This code was just the easiest to show. When the keyboard appears, there is a call to AdjustScreen, which creates a similar assignment of a TranslateTransform to the _mainGrid.RenderTransform. Again, if I remove the assignment, no problem occurs (no animation occurs either). Otherwise, the same error described above will occur.
Any help would be greatly appreciated. Thanks!
Edit. Here is the StoryBoard from the xaml file:
<Storyboard x:Key="HideKeyboard">
<DoubleAnimationUsingKeyFrames AccelerationRatio=".75" BeginTime="00:00:00" DecelerationRatio=".25" Storyboard.TargetName="KeyboardGrid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0" />
<!--<SplineDoubleKeyFrame KeyTime="00:00:00.20" Value="-10" />-->
<!--<SplineDoubleKeyFrame KeyTime="00:00:00.45" Value="450" />-->
<SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="450" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>`enter code here`
Also, I have a workaround for this that basically stores the _mainGrid.RenderTransfrom prior to changing it here. Then, when the HideKeybaord_Completed handler is called, it reverts it back. This method works. But it seems pretty hackish.
The crash is an application crash. Most of the time, we are exiting the UI anyway, so no one ever noticed. But, as I am adding a new view to the model, it crashes when closing my view and so it doesn't get back to the previous view.
This error can appear if you don't have a TransformGroup defined as the RenderTransform for the thing you are animating. It should look something like this:
<Ellipse>
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5" />
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
I attempted to make a Virtual Keyboard kind of behavior
xaml
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<UniformGrid Columns="5"
Rows="2"
x:Key="dummyKeyboard">
<Button Content="1" />
<Button Content="2" />
<Button Content="3" />
<Button Content="4" />
<Button Content="5" />
<Button Content="A" />
<Button Content="B" />
<Button Content="C" />
<Button Content="D" />
<Button Content="E" />
</UniformGrid>
</StackPanel.Resources>
<TextBox Text="regular textbox" />
<TextBox Text="virtual keyboard enabled textbox"
l:InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}" />
<TextBox Text="another regular textbox" />
<TextBox Text="another virtual keyboard enabled textbox"
l:InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}" />
<TextBox Text="one more regular textbox" />
</StackPanel>
for example purpose I have defined a dummyKeyboard containing some buttons. then I have assigned this keyboard to some of the textboxes by setting InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}", you can assign to all manually or even assign via styles for textbox
InputProvider class
namespace CSharpWPF
{
public class InputProvider : DependencyObject
{
public static object GetVirtualKeyboard(DependencyObject obj)
{
return (object)obj.GetValue(VirtualKeyboardProperty);
}
public static void SetVirtualKeyboard(DependencyObject obj, object value)
{
obj.SetValue(VirtualKeyboardProperty, value);
}
// Using a DependencyProperty as the backing store for VirtualKeyboard. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VirtualKeyboardProperty =
DependencyProperty.RegisterAttached("VirtualKeyboard", typeof(object), typeof(InputProvider), new PropertyMetadata(null, OnVirtualKeyboardChanged));
private static void OnVirtualKeyboardChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
tb.GotFocus += (sender, ee) => OpenPopup(sender as TextBox);
tb.LostFocus += (sender, ee) => ((Popup)tb.GetValue(PopupProperty)).IsOpen = false;
}
private static void OpenPopup(TextBox textBox)
{
Popup popup = new Popup() { Child = new ContentControl() { Content = GetVirtualKeyboard(textBox), Focusable = false } };
FocusManager.SetIsFocusScope(popup.Child, true);
popup.PlacementTarget = textBox;
popup.AllowsTransparency = true;
popup.PopupAnimation = PopupAnimation.Slide;
textBox.SetValue(PopupProperty, popup);
popup.IsOpen = true;
}
// Using a DependencyProperty as the backing store for Popup. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupProperty =
DependencyProperty.RegisterAttached("Popup", typeof(Popup), typeof(InputProvider), new PropertyMetadata(null));
}
}
this class defined an attached property VirtualKeyboard which is of type object, thus allowing you to define data templates as necessary
so this class listen to the GotFocus & LostFocus events of the textbox and displays or hides the virtual keyboard in a sliding animation.
give this a try, I hope this may help you to achieve the desired.
note that the dummy keyboard is really dummy it does not do any typing, you need to replace the same with your actual working virtual keyboard while implementing the same in your project.
I am try to set the caret/cursor position to the end of the string value in my WPF textbox when I open my window for the first time. I use the FocusManager to set the focus on my textbox when my window opens.
Nothing seems to work. Any ideas?
Note, I am using the MVVM pattern, and I included only a portion of the XAML from my code.
<Window
FocusManager.FocusedElement="{Binding ElementName=NumberOfDigits}"
Height="400" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0"
x:Name="NumberOfDigits"
IsReadOnly="{Binding Path=IsRunning, Mode=TwoWay}"
VerticalContentAlignment="Center"
Text="{Binding Path=Digits, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="0" Grid.Row="1"
Margin="10,0,10,0"
IsDefault="True"
Content="Start"
Command="{Binding StartCommand}"/>
</Grid>
</Window>
You can set the caret position using CaretIndex property of a TextBox. Please bear in mind that this is not a DependencyProperty. Nevertheless, you may still set it in XAML like this:
<TextBox Text="123" CaretIndex="{x:Static System:Int32.MaxValue}" />
Please remember to set CaretIndex after Text property or else it will not work. Thus it probably won't work if you bind to Text like in your example. In that case, simply use code-behind like this.
NumberOfDigits.CaretIndex = NumberOfDigits.Text.Length;
You may also create a Behavior, which, while still being code-behind, has the advantage of being reusable.
Example of a simple behavior class, using the focus event of the textbox:
class PutCursorAtEndTextBoxBehavior: Behavior<UIElement>
{
private TextBox _textBox;
protected override void OnAttached()
{
base.OnAttached();
_textBox = AssociatedObject as TextBox;
if (_textBox == null)
{
return;
}
_textBox.GotFocus += TextBoxGotFocus;
}
protected override void OnDetaching()
{
if (_textBox == null)
{
return;
}
_textBox.GotFocus -= TextBoxGotFocus;
base.OnDetaching();
}
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
_textBox.CaretIndex = _textBox.Text.Length;
}
}
Then, in your XAML, you attach the behavior like so:
<TextBox x:Name="MyTextBox" Text="{Binding Value}">
<i:Interaction.Behaviors>
<behaviors:PutCursorAtEndTextBoxBehavior/>
</i:Interaction.Behaviors>
</TextBox>
This worked for me. I am also using the MVVM pattern. However, my purpose for using a MMVM is to make unit testing possible and to make it easier to update my UI (loosely coupled). I don't see myself unit testing the position of the cursor so I don't mind resorting to the code behind for this simple task.
public ExpeditingLogView()
{
InitializeComponent();
this.Loaded += (sender, args) =>
{
Description.CaretIndex = Description.Text.Length;
Description.ScrollToEnd();
Description.Focus();
};
}
#Louis solution will not work if textbox used in template binding or any type of lazy bindings or lazy value assignments
So if the textbox used for example in Datagrid cell as a template that solution will need for tiny modification to work
and that is subscribing to text changed event
class PutCursorAtEndTextBoxBehavior : Behavior<UIElement>
{
private TextBox _textBox;
protected override void OnAttached()
{
base.OnAttached();
_textBox = AssociatedObject as TextBox;
if (_textBox == null)
{
return;
}
_textBox.GotFocus += TextBoxGotFocus;
// to make it work with binding
_textBox.TextChanged += TextBoxGotFocus;
}
protected override void OnDetaching()
{
if (_textBox == null)
{
return;
}
_textBox.GotFocus -= TextBoxGotFocus;
_textBox.TextChanged -= TextBoxGotFocus;
base.OnDetaching();
}
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
_textBox.CaretIndex = _textBox.Text.Length;
}
}
None of the answers here worked for me. I'm using binding for the TextBox and needed to move the caret right after the window poped up. This did it for me:
public MyWindow()
{
InitializeComponent();
ContentRendered += (sender, args) =>
{
MyTextBox.CaretIndex = MyTextBox.Text.Length;
MyTextBox.ScrollToEnd(); // not necessary for single line texts
MyTextBox.Focus();
};
}
Similar to Ceranski answer. Instead of adding to the Loaded event we add to ContentRendered.
In case of multiline TextBox setting cursor is not enough.
Try this:
NumberOfDigits.ScrollToEnd();
In WPF if line is long enough it is important also to scroll to the end of the line. So i'm using the following lines:
text_Box.Text = text;
text_Box.CaretIndex = text.Length;
text_Box.ScrollToHorizontalOffset(double.MaxValue);
// or you can use this - for me works also
// text_Box.ScrollToHorizontalOffset(text_Box.GetRectFromCharacterIndex(openFileDialog.FileName.Length).Right);
but read this caution (for me it's fine - probably already fixed):
TextBox ScrollToHorizontalOffset will not scroll after text is long enough
Try this given method: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/position-the-cursor-at-the-beginning-or-end-of-text?view=netframeworkdesktop-4.8
textBox.Select(2,0);
For some reasons I had to use :
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
textBox.CaretIndex = textBox.Text.Length;
textBox.ScrollToEnd();
}));
I wanted to create a UserControl / View with a pre-populated textbox bound to a ViewModel, and when the control opens up, focus is automatically set on the textbox and the caret position at the end. This was the only way I got it to work:
public TextBoxDialogView()
{
InitializeComponent();
TextBox.GotKeyboardFocus += (sender, args) =>
{
TextBox.CaretIndex = TextBox.Text.Length;
};
_ = TextBox.Focus();
}
Seems to work nicely so far...