WPF: Как уменьшить налет линейной толщины только по одной оси
В WPF я пытаюсь нарисовать линии графика в UserControl. Мне нужно будет масштабировать значения X и Y на разные величины.
У меня есть пример проекта, который я включу ниже. Проблема в том, что когда я использую RenderTransform для масштабирования линии графика, LineThickness также масштабируется. Обычно это не было бы проблемой, потому что я мог бы накинуть толщину линии обратно пропорционально масштабированному значению: LineThickness = требуемый LineThickness * (1/scaledValue)
К сожалению, в этом приложении мне нужно будет масштабировать размеры X и Y очень разными значениями, а LineThickness не разделяется на значения X и Y.
В моем примере ниже я рисую синусоидальную волну, которая масштабируется до размера поверхности рисования пользовательского элемента управления. Вы можете видеть, что пики и впадины простираются за пределы поверхности рисунка. Это связано с тем, что исходные данные изменяются от 1,0 до -1,0 и масштабируются до высоты поверхности рисования и переводятся в середину поверхности рисования.
Чтобы продемонстрировать это более четко, я также рисую синюю линию от 0,0 до 90,90, а затем от 90,90 по горизонтали вправо. Эта линия также масштабируется до высоты поверхности рисования. Вы можете видеть, что горизонтальная линия очень толстая.
Мне нужен способ удаления этих линий по оси Y отдельно от оси X, который в этом случае не масштабируется.
Я пробовал несколько подходов, включая создание объекта из линий. Я думал, что мог бы поставить конечные точки линий в масштабированном положении, а затем при рисовании реальных линий я использовал бы преобразование рендеринга для удаления накипи значений Y, но я никогда не мог заставить это работать из-за всей проблемы с прокси связывания,
У кого-нибудь есть идеи или с этим уже сталкивались? Я не могу быть первым с этой проблемой.
Вот код:
UserControl1.xaml.cs:
namespace WpfExampleControlLibrary
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
#region Constructor
public UserControl1()
{
InitializeComponent();
GraphPens = new ObservableCollection<GraphPen>();
}
#endregion Constructor
#region Public Methods
#endregion Public Methods
#region Dependency Properties
// Pens
public static PropertyMetadata GraphPenMetadata = new PropertyMetadata(null);
public static DependencyProperty GraphPensProperty
= DependencyProperty.Register(
"GraphPens",
typeof(ObservableCollection<GraphPen>),
typeof(UserControl1),
GraphPenMetadata);
public ObservableCollection<GraphPen> GraphPens
{
get { return (ObservableCollection<GraphPen>)GetValue(GraphPensProperty); }
set { SetValue(GraphPensProperty, value); }
}
// Debug Text
public static PropertyMetadata DebugTextMetadata = new PropertyMetadata(null);
public static DependencyProperty DebugTextProperty
= DependencyProperty.Register(
"DebugText",
typeof(string),
typeof(UserControl1),
DebugTextMetadata);
public string DebugText
{
get { return (string)GetValue(DebugTextProperty); }
set { SetValue(DebugTextProperty, value); }
}
#endregion Dependency Properties
private void DrawingSurface_SizeChanged(object sender, SizeChangedEventArgs e)
{
foreach (GraphPen graphPen in GraphPens)
{
graphPen.SetDrawingDimensions(e.NewSize.Height, e.NewSize.Width);
}
}
}
}
UserControl.xaml:
<UserControl Name="ExampleControl"
x:Class="WpfExampleControlLibrary.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfExampleControlLibrary"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:BindingProxy
x:Key="UserControlBindingProxy"
Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<DataTemplate DataType="{x:Type local:GraphPen}">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.RenderTransform>
<TransformGroup>
<TransformGroup.Children>
<ScaleTransform
CenterX="0.0"
CenterY="0.0"
ScaleX="1.0"
ScaleY="{Binding ScaleHeight}"/>
<TranslateTransform
X="0.0"
Y="{Binding TranslateHeight}"/>
</TransformGroup.Children>
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<Path
Data="{Binding PenGeometry}"
StrokeThickness="{Binding PenLineThickness}"
Stroke="{Binding
PenLineColor,
PresentationTraceSources.TraceLevel=None}"
/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<ItemsControl x:Name="DrawingSurface" Grid.Column="0" Grid.Row="0" SizeChanged="DrawingSurface_SizeChanged">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Aquamarine">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5"/>
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource UserControlBindingProxy},
Path=Data.GraphPens,
Mode=OneWay}"/>
<Line X1="10" Y1="0" X2="200" Y2="180" Stroke="DarkRed" StrokeThickness="2"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
<TextBox
x:Name="debug"
Grid.Column="0" Grid.Row="1"
Text="{Binding
Source={StaticResource UserControlBindingProxy},
Path=Data.DebugText,
Mode=OneWay}"/>
</Grid>
</UserControl>
GraphPen.cs:
namespace WpfExampleControlLibrary
{
public class GraphPen : DependencyObject
{
private double _drawingSurfaceHeight = 100;
#region Constructor
public GraphPen()
{
PenGeometry = new PathGeometry();
PenGeometry.Figures.Add(new PathFigure());
}
#endregion Constructor
#region Dependency Properties
// LineColor
public static PropertyMetadata PenLineColorPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenLineColorProperty
= DependencyProperty.Register(
"PenLineColor",
typeof(Brush),
typeof(GraphPen),
PenLineColorPropertyMetadata);
public Brush PenLineColor
{
get { return (Brush)GetValue(PenLineColorProperty); }
set { SetValue(PenLineColorProperty, value); }
}
// LineThickness
public static PropertyMetadata PenLineThicknessPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenLineThicknessProperty
= DependencyProperty.Register(
"PenLineThickness",
typeof(Int32),
typeof(GraphPen),
PenLineThicknessPropertyMetadata);
public Int32 PenLineThickness
{
get { return (Int32)GetValue(PenLineThicknessProperty); }
set { SetValue(PenLineThicknessProperty, value); }
}
// PenYMin
public static PropertyMetadata PenYMinPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenYMinProperty
= DependencyProperty.Register(
"PenYMin",
typeof(Double),
typeof(GraphPen),
PenYMinPropertyMetadata);
public double PenYMin
{
get { return (double)GetValue(PenYMinProperty); }
set { SetValue(PenYMinProperty, value); }
}
// PenYMax
public static PropertyMetadata PenYMaxPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenYMaxProperty
= DependencyProperty.Register(
"PenYMax",
typeof(Double),
typeof(GraphPen),
PenYMaxPropertyMetadata);
public double PenYMax
{
get { return (double)GetValue(PenYMaxProperty); }
set { SetValue(PenYMaxProperty, value); }
}
// ScaleHeight
public static PropertyMetadata ScaleHeightPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty ScaleHeightProperty
= DependencyProperty.Register(
"ScaleHeight",
typeof(Double),
typeof(GraphPen),
ScaleHeightPropertyMetadata);
public double ScaleHeight
{
get { return (double)GetValue(ScaleHeightProperty); }
set { SetValue(ScaleHeightProperty, value); }
}
// TranslateHeight
public static PropertyMetadata TranslateHeightPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty TranslateHeightProperty
= DependencyProperty.Register(
"TranslateHeight",
typeof(Double),
typeof(GraphPen),
TranslateHeightPropertyMetadata);
public double TranslateHeight
{
get { return (double)GetValue(TranslateHeightProperty); }
set { SetValue(TranslateHeightProperty, value); }
}
// Pen Geometry
public static PropertyMetadata PenGeometryMetadata = new PropertyMetadata(null);
public static DependencyProperty PenGeometryProperty
= DependencyProperty.Register(
"PenGeometry",
typeof(PathGeometry),
typeof(GraphPen),
PenGeometryMetadata);
public PathGeometry PenGeometry
{
get { return (PathGeometry)GetValue(PenGeometryProperty); }
set { SetValue(PenGeometryProperty, value); }
}
#endregion Dependency Properties
public void SetDrawingDimensions(double height, double width)
{
double dataHeight;
_drawingSurfaceHeight = height;
dataHeight = PenYMax - PenYMin;
ScaleHeight = _drawingSurfaceHeight / dataHeight;
TranslateHeight = ScaleHeight * -PenYMin;
}
}
}
MainWindow.xaml:
<Window x:Class="POC_WPF_UserControlExample.MainWindow"
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:Exmpl="clr-namespace:WpfExampleControlLibrary;assembly=WpfExampleControlLibrary"
xmlns:local="clr-namespace:POC_WPF_UserControlExample"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="550">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Exmpl:UserControl1 Grid.Column="1" Grid.Row="1" x:Name="myExample"/>
</Grid>
</Window>
MainWindow.xaml.cs:
namespace POC_WPF_UserControlExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DispatcherTimer _timer = null;
private GraphPen _graphPen0 = null;
private GraphPen _graphPen1 = null;
private Int32 _pos = 0;
private bool _firstTime = true;
public MainWindow()
{
InitializeComponent();
_graphPen0 = new GraphPen();
_graphPen0.PenLineColor = Brushes.DarkGoldenrod;
_graphPen0.PenLineThickness = 1;
_graphPen0.PenYMax = 1.0;
_graphPen0.PenYMin = -1.0;
myExample.GraphPens.Add(_graphPen0);
_graphPen1 = new GraphPen();
_graphPen1.PenLineColor = Brushes.DarkBlue;
_graphPen1.PenLineThickness = 1;
_graphPen1.PenYMax = 10.0;
_graphPen1.PenYMin = 0.0;
myExample.GraphPens.Add(_graphPen1);
_timer = new DispatcherTimer();
_timer.Tick += Timer_Tick;
_timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
_timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
_pos++;
Point penPoint0 = new Point(_pos, Math.Sin(_pos % 360));
Point penPoint1 = new Point(0,0);
if (_pos <= 9) penPoint1 = new Point(_pos, _pos);
if (_pos > 9) penPoint1 = new Point(_pos, 9);
if (_firstTime)
{
myExample.GraphPens[0].PenGeometry.Figures[0].StartPoint = penPoint0;
myExample.GraphPens[1].PenGeometry.Figures[0].StartPoint = penPoint1;
_firstTime = false;
}
else
{
LineSegment segment0 = new LineSegment(penPoint0, true);
myExample.GraphPens[0].PenGeometry.Figures[0].Segments.Add(segment0);
LineSegment segment1 = new LineSegment(penPoint1, true);
myExample.GraphPens[1].PenGeometry.Figures[0].Segments.Add(segment1);
}
myExample.DebugText = _pos.ToString();
}
}
}
РЕДАКТИРОВАТЬ постером
Я забыл показать BindingProcy.cs
namespace WpfExampleControlLibrary
{
public class BindingProxy : Freezable
{
#region Override Freezable Abstract Parts
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion Override Freezable Abstract Parts
#region Dependency Properties
// Using a DependencyProperty as the backing store for Data.
// This enables animation, styling, binding, etc...
public static PropertyMetadata DataMetadata = new PropertyMetadata(null);
public static readonly DependencyProperty DataProperty
= DependencyProperty.Register(
"Data",
typeof(object),
typeof(BindingProxy),
DataMetadata);
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
#endregion Dependency Properties
}
}
РЕДАКТИРОВАТЬ постером
Вот снимок экрана: