I have a Grid in my application and I want to move it around using four buttons (up, down, left, right). I have the grid's position (X and Y), but I don't know how to set a new position.
On the keyboard down event, you can change the Margin property of the grid. This will work best if you have the grid nested within another Grid or Canvas. You have the keep the parent container in mind and how it will work with the layout.
Assuming it is nested in another Grid control, here is a sample of what the code might look like:
private void OnKeyDown( object sender, System.Windows.Input.KeyEventArgs e )
{
if( e.Key == System.Windows.Input.Key.Up )
{
Thickness orig = MyGrid.Margin;
MyGrid.Margin = new Thickness( margin.Left, margin.Up - 5, margin.Right, margin.Bottom );
}
else if( ... )
...
}
NOTE: You probably don't even need to allocate a new Thickness object. Just change the one that is there. This is purely for example's sake.
The way to move the grid depends on the container that it is contained in. If it is placed inside a Canvas then you can just bind the Canvas.Left and Canvas.Top properties of the grid to some properties in your viewmodel and you can then change those numbers on up and down buttons like so:
<Canvas Width="400" Height="400">
<Grid Height="20" Width="20" Canvas.Left="{Binding GridLeft}" Canvas.Top="{Binding GridTop}" Background="Red" />
</Canvas>
And ViewModel will be like this
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
int gridLeft;
public int GridLeft
{
get { return gridLeft; }
set
{
gridLeft = value;
PropertyChanged(this, new PropertyChangedEventArgs("GridLeft"));
}
}
int gridTop;
public int GridTop
{
get { return gridTop; }
set
{
gridTop = value;
PropertyChanged(this, new PropertyChangedEventArgs("GridTop"));
}
}
}
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 have a usercontrol which I want to animate via rendertransform. (rot, trans, scale)
It works out fine, but the control is on a layer below layers with other controls, which are created by itemcontrols with itemsources.
The usercontrol should not know about it's parents so I guess i'm looking for something like a universal render zindex.
My idea was to use a popup but it comes with peformance disadvantages and mouse event complications.
(that was with a bitmapcachebrush )
I guess i cant take out the item from the itemssource of the itemscontrol and put it on a front layer.
Is an adorner what I am looking for? Also it doesnt have to / shouldnt be responsive to mouse events. (the canvas parent around it handling mouse enter and leave is)
Are there other possibilities?
1.custom control xaml cutout
2.custom control mouse event
3.ui element adorner class
<Grid x:Name="adholder" Width="{Binding ElementName=thiscontrol, Path=ActualWidth}">
<!-- Add adorning content here -->
<Grid x:Name="gridAnimation" IsHitTestVisible="False"
Height="{Binding ElementName=thiscontrol, Path=ActualHeight}"
Width="{Binding ElementName=thiscontrol, Path=ActualWidth}">
<Grid.CacheMode>
<BitmapCache x:Name="bmpcache" EnableClearType="True"
RenderAtScale="1"
SnapsToDevicePixels="False"
/>
</Grid.CacheMode>
private void thiscontrol_MouseEnter(object sender, MouseEventArgs e)
{
if (StoryboardEnter != null)
{
layer = AdornerLayer.GetAdornerLayer(gridAnimation);
adholder.Children.Clear();
adc = new AdornerContentPresenter(adholder);
adc.Content = gridAnimation;
layer.Add(adc);
StoryboardEnter.RunAnim(gridAnimation, AnimationDirection.To, AnimationReset.Reset);
}
}
//not my code
public class AdornerContentPresenter : Adorner
{
private VisualCollection _Visuals;
private ContentPresenter _ContentPresenter;
public AdornerContentPresenter(UIElement adornedElement)
: base(adornedElement)
{
_Visuals = new VisualCollection(this);
_ContentPresenter = new ContentPresenter();
_Visuals.Add(_ContentPresenter);
}
public AdornerContentPresenter(UIElement adornedElement, Visual content)
: this(adornedElement)
{ Content = content; }
protected override Size MeasureOverride(Size constraint)
{
_ContentPresenter.Measure(constraint);
return _ContentPresenter.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
_ContentPresenter.Arrange(new Rect(0, 0,
finalSize.Width, finalSize.Height));
return _ContentPresenter.RenderSize;
}
protected override Visual GetVisualChild(int index)
{ return _Visuals[index]; }
protected override int VisualChildrenCount
{ get { return _Visuals.Count; } }
public object Content
{
get { return _ContentPresenter.Content; }
set { _ContentPresenter.Content = value; }
}
}
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 a DataGrid which is bound to an ObservableCollection ProductsFound
which is exposed as a property in my ViewModel.
By typing text in a TextBox, products contained in the model that have the Code property that contains the text inserted in the TextBox are added to ProductsFound.
I found out that if the DataGrid is contained in any control such as a StackPanel or a TabItem, the Window (the program) stops responding when I try to type text into the TextBox; while if the DataGrid isn't contained in any control, everything runs normally.
Here's the code for the window:
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// This method just fill the dataset I pass to the model's contructor in the next line.
Init();
ProductsModel model = new ProductsModel(dataSet);
searchViewModel = new ProductsSearchViewModel(model);
DataContext = searchViewModel;
}
private ProductsSearchViewModel searchViewModel;
// This handler supports the binding between the TextBox and the MatchText property of the View Model.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
And here's my ViewModel:
public class ProductsSearchViewModel : Notifier, IProductsSearchViewModel
{
public ProductsSearchViewModel(IProductsModel inModel)
{
model = inModel;
productsFound = new ObservableCollection<ProductViewModel>();
}
private string matchText;
private IProductsModel model;
private ObservableCollection<ProductViewModel> productsFound;
// This is a helper method that search for the products in the model and adds them to ProductsFound.
private void Search(string text)
{
Results.Clear();
foreach (Product product in model.Products)
{
if (product.Code.ToLower().Contains(text.ToLower()))
Results.Add(new ProductViewModel(product));
}
}
public string MatchText
{
get { return matchText; }
// This setter is meant to be executed every time the Text property of the TextBox is changed.
set
{
if ((value != matchText) && (value != ""))
{
matchText = value;
// This raises INotifyPropertyChanged.PropertyChaged.
NotifyPropertyChanged("MatchText");
Search(value);
}
}
}
public ObservableCollection<ProductViewModel> ProductsFound
{
get
{
return productsFound;
}
set
{
productsFound = value;
NotifyPropertyChanged("Results");
}
}
}
Here's the XAML:
<Window x:Class="MyNameSpace.UI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<TextBox Text="{Binding MatchText, Mode=TwoWay}" TextChanged="TextBox_TextChanged" />
<DataGrid x:Name="grid1" ItemsSource="{Binding Results}" >
</StackPanel>
</Grid>
With that StackPanel the program stops responding when I try to type text in the Textbox and no item is added to the DataGrid; but if i remove it everything runs ok.
What could the problem be? Am I missing something in how the WPF binding system works?
Is my view model coded wrong?
Thanks in advance.
Putting that StackPanel there prevents the DataGrid from acquiring a specific Height, thus it just expands down to infinity, and that breaks UI Virtualization.
Remove the StackPanel from there and use a non-infinite container, such as Grid or DockPanel.
This is going to be straight forward no doubt, but for what ever reason, my mind is drawing a blank on it.
I've got a small, non-resizeable window (325x450) which has 3 Expanders in it, stacked vertically. Each Expander contains an ItemsControl that can potentially have a lot of items in and therefore need to scroll.
What I can't seem to get right is how to layout the Expanders so that they expand to fill any space that is available without pushing other elements off the screen. I can sort of achieve what I'm after by using a Grid and putting each expander in a row with a * height, but this means they are always taking up 1/3 of the window each which defeats the point of the Expander :)
Crappy diagram of what I'm trying to achieve:
This requirement is a little unusal because the you want the state of the Children in the Grid to decide the Height of the RowDefinition they are in.
I really like the layout idea though and I can't believe I never had a similar requirement myself.. :)
For a reusable solution I would use an Attached Behavior for the Grid.
The behavior will subscribe to the Attached Events Expander.Expanded and Expander.Collapsed and in the event handlers, get the right RowDefinition from Grid.GetRow and update the Height accordingly. It works like this
<Grid ex:GridExpanderSizeBehavior.SizeRowsToExpanderState="True">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Expander Grid.Row="0" ... />
<Expander Grid.Row="1" ... />
<Expander Grid.Row="2" ... />
<!-- ... -->
</Grid>
And here is GridExpanderSizeBehavior
public class GridExpanderSizeBehavior
{
public static DependencyProperty SizeRowsToExpanderStateProperty =
DependencyProperty.RegisterAttached("SizeRowsToExpanderState",
typeof(bool),
typeof(GridExpanderSizeBehavior),
new FrameworkPropertyMetadata(false, SizeRowsToExpanderStateChanged));
public static void SetSizeRowsToExpanderState(Grid grid, bool value)
{
grid.SetValue(SizeRowsToExpanderStateProperty, value);
}
private static void SizeRowsToExpanderStateChanged(object target, DependencyPropertyChangedEventArgs e)
{
Grid grid = target as Grid;
if (grid != null)
{
if ((bool)e.NewValue == true)
{
grid.AddHandler(Expander.ExpandedEvent, new RoutedEventHandler(Expander_Expanded));
grid.AddHandler(Expander.CollapsedEvent, new RoutedEventHandler(Expander_Collapsed));
}
else if ((bool)e.OldValue == true)
{
grid.RemoveHandler(Expander.ExpandedEvent, new RoutedEventHandler(Expander_Expanded));
grid.RemoveHandler(Expander.CollapsedEvent, new RoutedEventHandler(Expander_Collapsed));
}
}
}
private static void Expander_Expanded(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
Expander expander = e.OriginalSource as Expander;
int row = Grid.GetRow(expander);
if (row <= grid.RowDefinitions.Count)
{
grid.RowDefinitions[row].Height = new GridLength(1.0, GridUnitType.Star);
}
}
private static void Expander_Collapsed(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
Expander expander = e.OriginalSource as Expander;
int row = Grid.GetRow(expander);
if (row <= grid.RowDefinitions.Count)
{
grid.RowDefinitions[row].Height = new GridLength(1.0, GridUnitType.Auto);
}
}
}
If you don't mind a little code-behind, you could probably hook into the Expanded/Collapsed events, find the parent Grid, get the RowDefinition for the expander, and set the value equal to * if its expanded, or Auto if not.
For example,
Expander ex = sender as Expander;
Grid parent = FindAncestor<Grid>(ex);
int rowIndex = Grid.GetRow(ex);
if (parent.RowDefinitions.Count > rowIndex && rowIndex >= 0)
parent.RowDefinitions[rowIndex].Height =
(ex.IsExpanded ? new GridLength(1, GridUnitType.Star) : GridLength.Auto);
And the FindAncestor method is defined as this:
public static T FindAncestor<T>(DependencyObject current)
where T : DependencyObject
{
// Need this call to avoid returning current object if it is the
// same type as parent we are looking for
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}