Привязка Прямоугольника формы WPF
Вопрос
Я пытаюсь создать какую-то форму в wpf, которая изменяет размер в соответствии с содержимым (которое должно быть текстом).К сожалению, свойство stretch не является правильным, так как я хочу, чтобы была изменена только ширина фигуры и без границы (пожалуйста, скопируйте нижний пример в xamlpad, чтобы убедиться в этом сами) этой формы растянуты.Границы должны оставаться такими, какие они есть, или, по крайней мере, равномерно масштабироваться.Я перепробовал много идей.С различными фрагментами формы в виде сетки, стековой панели или с обрезанной панелью и т.д.Мой следующий подход был бы следующим:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><Page.Resources>
<LinearGradientBrush StartPoint="0.0,1" EndPoint="0.0,0" x:Key="brushYellow">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.000000" Color="#fffef4a6"/>
<GradientStop Offset="0.175824" Color="#fffef9d6"/>
<GradientStop Offset="0.800000" Color="#fffef9d6"/>
<GradientStop Offset="1.000000" Color="#fffef4a6"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush></Page.Resources><Grid>
<Path Stroke="#fffce90d" StrokeThickness="1" Fill="{StaticResource brushYellow}">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry RadiusX="15" RadiusY="15">
<!--RectangleGeometry.Rect>
<Binding StringFormat="{}{0 0 {0} 82}" ElementName="Text" Path="Width"/>
</RectangleGeometry.Rect-->
<RectangleGeometry.Rect>
<Rect Width="150" Height="82"/>
</RectangleGeometry.Rect>
</RectangleGeometry>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True" StartPoint="0,15">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="17,41" />
<LineSegment Point="0,67" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
<TextBox Name="Text" Background="Transparent" BorderThickness="0" MinWidth="150" Margin="0"/>
</Grid></Page>
Это будет работать прямо из коробки в xamlpad.Раскомментированная часть в строке 19 - это то, чего я действительно хочу достичь:Привязка прямой части прямоугольника к чему-то другому.К сожалению, ширина Rect не равна dp, вот почему я использую привязку stringformatted непосредственно к самому Rect.
Как и ожидалось от life, это не работает (ничего не видно) : D Что я здесь делаю не так?
Решение
Я использую набор классов с именами ViewboxPath, ViewboxLine, ViewboxPolyline и т.д., Которые изменяют растянутую семантику Shape, чтобы она была немного более податливой.Я не уверен, что понял ваш вопрос, поэтому я не знаю, решит ли моя методика вашу проблему или нет.
Как я читаю, либо вы хотите контролировать растяжение, которое обеспечит это решение, либо вы хотите, чтобы штрихи растягивались вместе с изображением, что обеспечит ответ Сэма.
Во всяком случае, ниже приведен код для этих классов, и вот как вы их используете:
<edf:ViewboxPolyline
Viewbox="0 0 1 1" <!-- Actually the default, can be omitted -->
Stretch="Fill" <!-- Also default, can be omitted -->
Stroke="Blue"
Points="0,0 0.2,0 0.2,0.3 0.4,0.3" />
<edf:ViewboxPolygon
Viewbox="0 0 10 10"
Stroke="Blue"
Points="5,0 10,5 5,10 0,5" />
<edf:ViewboxPath
Viewbox="0 0 10 10"
Stroke="Blue"
Data="M10,5 L4,4 L5,10" />
Мой Классы формы окна просмотра используются так же, как обычные формы (Polyline
, Polygon
, Path
и Line
) за исключением дополнительных Viewbox
параметр, и тот факт, что они по умолчанию равны Stretch="Fill"
.Параметр Viewbox указывает в системе координат, используемой для задания формы, область геометрии, которая должна быть растянута с помощью Fill
, Uniform
или UniformToFill
настройки, вместо использования Geometry.GetBounds
.
Это обеспечивает очень точный контроль над растягиванием и облегчает точное выравнивание отдельных фигур друг с другом.
Вот фактический код для моего Классы формы окна просмотра, включая абстрактный базовый класс ViewboxShape
который содержит общую функциональность:
public abstract class ViewboxShape : Shape
{
Matrix _transform;
Pen _strokePen;
Geometry _definingGeometry;
Geometry _renderGeometry;
static ViewboxShape()
{
StretchProperty.OverrideMetadata(typeof(ViewboxShape), new FrameworkPropertyMetadata
{
AffectsRender = true,
DefaultValue = Stretch.Fill,
});
}
// The built-in shapes compute stretching using the actual bounds of the geometry.
// ViewBoxShape and its subclasses use this Viewbox instead and ignore the actual bounds of the geometry.
public Rect Viewbox { get { return (Rect)GetValue(ViewboxProperty); } set { SetValue(ViewboxProperty, value); } }
public static readonly DependencyProperty ViewboxProperty = DependencyProperty.Register("Viewbox", typeof(Rect), typeof(ViewboxShape), new UIPropertyMetadata
{
DefaultValue = new Rect(0,0,1,1),
});
// If defined, replaces all the Stroke* properties with a single Pen
public Pen Pen { get { return (Pen)GetValue(PenProperty); } set { SetValue(PenProperty, value); } }
public static readonly DependencyProperty PenProperty = DependencyProperty.Register("Pen", typeof(Pen), typeof(Pen), new UIPropertyMetadata
{
DefaultValue = null
});
// Subclasses override this to define geometry if caching is desired, or just override DefiningGeometry
protected virtual Geometry ComputeDefiningGeometry()
{
return null;
}
// Subclasses can use this PropertyChangedCallback for properties that affect the defining geometry
protected static void OnGeometryChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var shape = sender as ViewboxShape;
if(shape!=null)
{
shape._definingGeometry = null;
shape._renderGeometry = null;
}
}
// Compute viewport from box & constraint
private Size ApplyStretch(Stretch stretch, Rect box, Size constraint)
{
double uniformScale;
switch(stretch)
{
default:
return new Size(box.Width, box.Height);
case Stretch.Fill:
return constraint;
case Stretch.Uniform:
uniformScale = Math.Min(constraint.Width / box.Width, constraint.Height / box.Height);
break;
case Stretch.UniformToFill:
uniformScale = Math.Max(constraint.Width / box.Width, constraint.Height / box.Height);
break;
}
return new Size(uniformScale * box.Width, uniformScale * box.Height);
}
protected override Size MeasureOverride(Size constraint)
{
// Clear pen cache if settings have changed
if(_strokePen!=null)
if(Pen!=null)
_strokePen = null;
else
if(_strokePen.Thickness != StrokeThickness ||
_strokePen.Brush != Stroke ||
_strokePen.StartLineCap != StrokeStartLineCap ||
_strokePen.EndLineCap != StrokeEndLineCap ||
_strokePen.DashCap != StrokeDashCap ||
_strokePen.LineJoin != StrokeLineJoin ||
_strokePen.MiterLimit != StrokeMiterLimit ||
_strokePen.DashStyle.Dashes != StrokeDashArray ||
_strokePen.DashStyle.Offset != StrokeDashOffset)
_strokePen = null;
_definingGeometry = null;
_renderGeometry = null;
return ApplyStretch(Stretch, Viewbox, constraint);
}
protected override Size ArrangeOverride(Size availableSize)
{
Stretch stretch = Stretch;
Size viewport;
Matrix transform;
// Compute new viewport and transform
if(stretch==Stretch.None)
{
viewport = availableSize;
transform = Matrix.Identity;
}
else
{
Rect box = Viewbox;
viewport = ApplyStretch(stretch, box, availableSize);
double scaleX = viewport.Width / box.Width;
double scaleY = viewport.Height / box.Height;
transform = new Matrix(scaleX, 0, 0, scaleY, -box.Left * scaleX, -box.Top * scaleY);
}
if(_transform!=transform)
{
_transform = transform;
_renderGeometry = null;
InvalidateArrange();
}
return viewport;
}
protected Pen PenOrStroke
{
get
{
if(Pen!=null)
return Pen;
if(_strokePen==null)
_strokePen = new Pen
{
Thickness = StrokeThickness,
Brush = Stroke,
StartLineCap = StrokeStartLineCap,
EndLineCap = StrokeEndLineCap,
DashCap = StrokeDashCap,
LineJoin = StrokeLineJoin,
MiterLimit = StrokeMiterLimit,
DashStyle =
StrokeDashArray.Count==0 && StrokeDashOffset==0 ? DashStyles.Solid :
new DashStyle(StrokeDashArray, StrokeDashOffset),
};
return _strokePen;
}
}
protected Matrix Transform
{
get
{
return _transform;
}
}
protected override Geometry DefiningGeometry
{
get
{
if(_definingGeometry==null)
_definingGeometry = ComputeDefiningGeometry();
return _definingGeometry;
}
}
protected Geometry RenderGeometry
{
get
{
if(_renderGeometry==null)
{
Geometry defining = DefiningGeometry;
if(_transform==Matrix.Identity || defining==Geometry.Empty)
_renderGeometry = defining;
else
{
Geometry geo = defining.CloneCurrentValue();
if(object.ReferenceEquals(geo, defining)) geo = defining.Clone();
geo.Transform = new MatrixTransform(
geo.Transform==null ? _transform : geo.Transform.Value * _transform);
_renderGeometry = geo;
}
}
return _renderGeometry;
}
}
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawGeometry(Fill, PenOrStroke, RenderGeometry);
}
}
[ContentProperty("Data")]
public class ViewboxPath : ViewboxShape
{
public Geometry Data { get { return (Geometry)GetValue(DataProperty); } set { SetValue(DataProperty, value); } }
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(Geometry), typeof(ViewboxPath), new UIPropertyMetadata
{
DefaultValue = Geometry.Empty,
PropertyChangedCallback = OnGeometryChanged,
});
protected override Geometry DefiningGeometry
{
get { return Data ?? Geometry.Empty; }
}
}
public class ViewboxLine : ViewboxShape
{
public double X1 { get { return (double)GetValue(X1Property); } set { SetValue(X1Property, value); } }
public double X2 { get { return (double)GetValue(X2Property); } set { SetValue(X2Property, value); } }
public double Y1 { get { return (double)GetValue(Y1Property); } set { SetValue(Y1Property, value); } }
public double Y2 { get { return (double)GetValue(Y2Property); } set { SetValue(Y2Property, value); } }
public static readonly DependencyProperty X1Property = DependencyProperty.Register("X1", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
public static readonly DependencyProperty X2Property = DependencyProperty.Register("X2", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
public static readonly DependencyProperty Y1Property = DependencyProperty.Register("Y1", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
public static readonly DependencyProperty Y2Property = DependencyProperty.Register("Y2", typeof(double), typeof(ViewboxLine), new FrameworkPropertyMetadata { PropertyChangedCallback = OnGeometryChanged, AffectsRender = true });
protected override Geometry ComputeDefiningGeometry()
{
return new LineGeometry(new Point(X1, Y1), new Point(X2, Y2));
}
}
[ContentProperty("Points")]
public class ViewboxPolyline : ViewboxShape
{
public ViewboxPolyline()
{
Points = new PointCollection();
}
public PointCollection Points { get { return (PointCollection)GetValue(PointsProperty); } set { SetValue(PointsProperty, value); } }
public static readonly DependencyProperty PointsProperty = DependencyProperty.Register("Points", typeof(PointCollection), typeof(ViewboxPolyline), new FrameworkPropertyMetadata
{
PropertyChangedCallback = OnGeometryChanged,
AffectsRender = true,
});
public FillRule FillRule { get { return (FillRule)GetValue(FillRuleProperty); } set { SetValue(FillRuleProperty, value); } }
public static readonly DependencyProperty FillRuleProperty = DependencyProperty.Register("FillRule", typeof(FillRule), typeof(ViewboxPolyline), new FrameworkPropertyMetadata
{
DefaultValue = FillRule.EvenOdd,
PropertyChangedCallback = OnGeometryChanged,
AffectsRender = true,
});
public bool CloseFigure { get { return (bool)GetValue(CloseFigureProperty); } set { SetValue(CloseFigureProperty, value); } }
public static readonly DependencyProperty CloseFigureProperty = DependencyProperty.Register("CloseFigure", typeof(bool), typeof(ViewboxPolyline), new FrameworkPropertyMetadata
{
DefaultValue = false,
PropertyChangedCallback = OnGeometryChanged,
AffectsRender = true,
});
protected override Geometry ComputeDefiningGeometry()
{
PointCollection points = Points;
if(points.Count<2) return Geometry.Empty;
var geometry = new StreamGeometry { FillRule = FillRule };
using(var context = geometry.Open())
{
context.BeginFigure(Points[0], true, CloseFigure);
context.PolyLineTo(Points.Skip(1).ToList(), true, true);
}
return geometry;
}
}
public class ViewboxPolygon : ViewboxPolyline
{
static ViewboxPolygon()
{
CloseFigureProperty.OverrideMetadata(typeof(ViewboxPolygon), new FrameworkPropertyMetadata
{
DefaultValue = true,
});
}
}
Наслаждайтесь!
Другие советы
Вы могли бы попробовать использовать преобразование, чтобы изменить размер прямоугольника, вместо того чтобы напрямую привязывать ширину прямоугольника.Я думаю, это должно сработать.
Например.поместите что-то вроде этого в свой Прямоугольная геометрия тег:
<RectangleGeometry.Transform>
<ScaleTransform ScaleX="{Binding ElementName=textBoxName, Path=Width,
Converter=MyScaleWidthConverter}" />
</RectangleGeometry.Transform>
Где Имя текстового поля это имя вашего текстового поля.Не мог заставить себя назвать это текстом - слишком запутанно.
Вам нужно будет поставить преобразователь, чтобы обеспечить правильное масштабирование, напримерВероятно, вы захотите вернуть что-то вроде Ширина / 150 приведен ваш пример кода.
Я вижу немного странное поведение, когда ширина прямоугольника установлена в значение Auto в Visual Studio designer - я думаю, что это, вероятно, причуда дизайнера.Должно сработать, как только конвертер будет подключен во время выполнения.
Как насчет использования вашего пути в качестве кисти? В следующем коде я использую DrawingBrush в качестве фона для самого TextBox или в качестве фона для вмещающей границы. Просто намек ... Надеюсь, это поможет.
<Window x:Class="MarkupWpf.BrushTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BrushTest" Height="300" Width="300">
<Window.Resources>
<LinearGradientBrush StartPoint="0.0,1" EndPoint="0.0,0" x:Key="brushYellow">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.000000" Color="#fffef4a6"/>
<GradientStop Offset="0.175824" Color="#fffef9d6"/>
<GradientStop Offset="0.800000" Color="#fffef9d6"/>
<GradientStop Offset="1.000000" Color="#fffef4a6"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<DrawingBrush x:Key="FabBrush">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="{StaticResource brushYellow}">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="#fffce90d" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry RadiusX="15" RadiusY="15">
<RectangleGeometry.Rect>
<Rect Width="150" Height="82"/>
</RectangleGeometry.Rect>
</RectangleGeometry>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True" StartPoint="0,15">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="17,41" />
<LineSegment Point="0,67" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Background="{StaticResource FabBrush}" BorderThickness="0" MinWidth="150" Margin="0"/>
<Grid Grid.Row="1">
<Border Background="{StaticResource FabBrush}">
<TextBox Grid.Row="0" BorderThickness="0" MinWidth="150" Margin="20" />
</Border>
</Grid>
</Grid>
</Window>
Я делаю что-то вроде этого. Я хочу, чтобы пользовательские фигуры автоматически меняли размер при изменении размера окна. для моего решения я вывел форму и переопределил свойство definingGeometry. Для размеров моей фигуры я использую свойства ActualWidth и ActualHeight, так как они отражают истинную ширину и высоту. я также overiding метод measureOverride (), как это
protected override Size MeasureOverride(Size constraint)
{
return this.DesiredSize;
}
Я генерирую фигуру в коде, используя (как я уже говорил) actualWidth и actualHeight в качестве максимальных значений. надеюсь, это поможет