我需要一个类似于Viewbox的布局控件,但拉伸规则略有不同。我从.NET参考源复制了Viewbox源,但是现在我在子控件中看到了奇怪的数据绑定问题。
这是一个简化的示例:
<Window
x:Class="CustomViewbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomViewbox"
Title="MainWindow" Height="450" Width="800"
>
<local:Viewbox Stretch="Uniform">
<Grid>
<StackPanel>
<TextBlock Foreground="Red" Margin="5" Text="{Binding Path=Round,StringFormat={}Round {0}}"/>
<Button Margin="5" Content="Next round" Click="next_round_Click"/>
</StackPanel>
</Grid>
</local:Viewbox>
</Window>
以及相应的代码隐藏:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace CustomViewbox
{
public class Game : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
int round = 1;
public int Round { get { return round; } set { round = value; NotifyPropertyChanged(); } }
}
public partial class MainWindow : Window
{
Game game;
public MainWindow()
{
InitializeComponent();
game = new Game();
DataContext = game;
}
private void next_round_Click(object sender, RoutedEventArgs e)
{
++game.Round;
}
}
}
运行时,TextBlock为空或不可见(不会折叠)。到目前为止,我发现了3种无法令人满意的解决方法:
有人可以解释发生了什么,或如何调试吗?
为完整起见,这里是修改后的Viewbox源代码,应该是重现我的问题所需的所有代码:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace CustomViewbox
{
public class Viewbox : Decorator
{
private ContainerVisual _internalVisual;
public static readonly DependencyProperty StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(Viewbox), new FrameworkPropertyMetadata(Stretch.Uniform, FrameworkPropertyMetadataOptions.AffectsMeasure), new ValidateValueCallback(ValidateStretchValue));
private static bool ValidateStretchValue(object value)
{
var s = (Stretch)value;
return s == Stretch.Uniform
|| s == Stretch.None
|| s == Stretch.Fill
|| s == Stretch.UniformToFill;
}
private ContainerVisual InternalVisual
{
get
{
if (_internalVisual == null)
{
_internalVisual = new ContainerVisual();
AddVisualChild(_internalVisual);
}
return _internalVisual;
}
}
private UIElement InternalChild
{
get
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) return vc[0] as UIElement;
else return null;
}
set
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) vc.Clear();
vc.Add(value);
}
}
private Transform InternalTransform
{
get
{
return InternalVisual.Transform;
}
set
{
InternalVisual.Transform = value;
}
}
public override UIElement Child
{
// everything is the same as on Decorator, the only difference is to insert intermediate Visual to specify scaling transform
get
{
return InternalChild;
}
set
{
UIElement old = InternalChild;
if (old != value)
{
//need to remove old element from logical tree
RemoveLogicalChild(old);
if (value != null)
{
AddLogicalChild(value);
}
InternalChild = value;
InvalidateMeasure();
}
}
}
protected override int VisualChildrenCount
{
get { return 1; /* Always have internal container visual */ }
}
protected override Visual GetVisualChild(int index)
{
if (index != 0)
{
throw new ArgumentOutOfRangeException("index", index, /*SR.Get(SRID.Visual_ArgumentOutOfRange)*/"argument out of range");
}
return InternalVisual;
}
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
/// <summary>
/// Updates DesiredSize of the Viewbox. Called by parent UIElement. This is the first pass of layout.
/// </summary>
/// <remarks>
/// Viewbox measures it's child at an infinite constraint; it allows the child to be however large it so desires.
/// The child's returned size will be used as it's natural size for scaling to Viewbox's size during Arrange.
/// </remarks>
/// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
/// <returns>The Decorator's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
var child = InternalChild;
var parentSize = new Size();
if (child != null)
{
// Initialize child constraint to infinity. We need to get a "natural" size for the child in absence of constraint.
// Note that an author *can* impose a constraint on a child by using Height/Width, &c... properties
var infiniteConstraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
child.Measure(infiniteConstraint);
var childSize = child.DesiredSize;
var scalefac = ComputeScaleFactor(constraint, childSize, Stretch);
parentSize.Width = scalefac * childSize.Width;
parentSize.Height = scalefac * childSize.Height;
if (parentSize.Width > constraint.Width)
parentSize.Width = constraint.Width;
childSize = new Size(constraint.Width / scalefac, constraint.Height / scalefac);
child.Measure(childSize);
}
return parentSize;
}
/// <summary>
/// Viewbox always sets the child to its desired size. It then computes and applies a transformation
/// from that size to the space available: Viewbox's own input size less child margin.
///
/// Viewbox also calls arrange on its child.
/// </summary>
/// <param name="arrangeSize">Size in which Border will draw the borders/background and children.</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
var child = InternalChild;
if (child != null)
{
var childSize = child.DesiredSize;
// Compute scaling factors from arrange size and the measured child content size
var scalefac = ComputeScaleFactor(arrangeSize, childSize, Stretch);
InternalTransform = new ScaleTransform(scalefac, scalefac);
childSize = new Size(arrangeSize.Width / scalefac, arrangeSize.Height / scalefac);
// Arrange the child to the desired size
child.Arrange(new Rect(new Point(), childSize));
// return the size occupied by scaled child
arrangeSize.Width = scalefac * childSize.Width;
arrangeSize.Height = scalefac * childSize.Height;
}
return arrangeSize;
}
/// <summary>
/// This is a helper function that computes scale factors depending on a target size and a content size
/// </summary>
/// <param name="availableSize">Size into which the content is being fitted.</param>
/// <param name="contentSize">Size of the content, measured natively (unconstrained).</param>
/// <param name="stretch">Value of the Stretch property on the element.</param>
internal static double ComputeScaleFactor(Size availableSize, Size contentSize, Stretch stretch)
{
// Compute scaling factors to use for axes
var scale = 1.0;
var isConstrainedHeight = !Double.IsPositiveInfinity(availableSize.Height);
if (isConstrainedHeight)
{
// Compute scaling factors for both axes
scale = (DoubleUtil.IsZero(contentSize.Height)) ? 0.0 : availableSize.Height / contentSize.Height;
}
return scale;
}
}
static class DoubleUtil
{
internal const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
internal const float FLT_MIN = 1.175494351e-38F; /* Number close to zero, where float.MinValue is -float.MaxValue */
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * DBL_EPSILON;
}
}
}
答案 0 :(得分:0)
事实证明,问题是Klaus Gütter建议在Viewbox类中缺少LogicalChildren的替代。我错误地认为一个人不能覆盖一个内部属性,所以我删除了它。但是事实证明,只要我可以从覆盖中删除内部关键字,就可以。
这是工作代码:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace CustomViewbox
{
/// <summary>
/// Returns an Enumerator that enumerates over nothing.
/// </summary>
internal class EmptyEnumerator : IEnumerator
{
// singleton class, private ctor
private EmptyEnumerator()
{
}
/// <summary>
/// Read-Only instance of an Empty Enumerator.
/// </summary>
public static IEnumerator Instance
{
get
{
if (_instance == null)
{
_instance = new EmptyEnumerator();
}
return _instance;
}
}
/// <summary>
/// Does nothing.
/// </summary>
public void Reset() { }
/// <summary>
/// Returns false.
/// </summary>
/// <returns>false</returns>
public bool MoveNext() { return false; }
#pragma warning disable 1634, 1691 // about to use PreSharp message numbers - unknown to C#
/// <summary>
/// Returns null.
/// </summary>
public object Current
{
get
{
#pragma warning disable 6503 // "Property get methods should not throw exceptions."
throw new InvalidOperationException();
#pragma warning restore 6503
}
}
#pragma warning restore 1634, 1691
private static IEnumerator _instance;
}
internal class SingleChildEnumerator : IEnumerator
{
internal SingleChildEnumerator(object Child)
{
_child = Child;
_count = Child == null ? 0 : 1;
}
object IEnumerator.Current
{
get { return (_index == 0) ? _child : null; }
}
bool IEnumerator.MoveNext()
{
_index++;
return _index < _count;
}
void IEnumerator.Reset()
{
_index = -1;
}
private int _index = -1;
private int _count = 0;
private object _child;
}
/// <summary>
/// </summary>
public class Viewbox : Decorator
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
/*static Viewbox()
{
ControlsTraceLogger.AddControl(TelemetryControls.ViewBox);
}*/
/// <summary>
/// Default DependencyObject constructor
/// </summary>
/// <remarks>
/// Automatic determination of current Dispatcher. Use alternative constructor
/// that accepts a Dispatcher for best performance.
/// </remarks>
public Viewbox() : base()
{
}
#endregion
//-------------------------------------------------------------------
//
// Public Fields
//
//-------------------------------------------------------------------
#region Public Fields
/// <summary>
/// This is the DependencyProperty for the Viewbox's Stretch property.
///
/// Default: Stretch.Uniform
/// <seealso cref="Viewbox.Stretch" />
/// </summary>
public static readonly DependencyProperty StretchProperty
= DependencyProperty.Register(
"Stretch", // Property name
typeof(Stretch), // Property type
typeof(Viewbox), // Property owner
new FrameworkPropertyMetadata(Stretch.Uniform, FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(ValidateStretchValue));
private static bool ValidateStretchValue(object value)
{
Stretch s = (Stretch)value;
return (s == Stretch.Uniform
|| s == Stretch.None
|| s == Stretch.Fill
|| s == Stretch.UniformToFill);
}
#endregion
//-------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
//-------------------------------------------------------------------
//
// Public Properties
//
//-------------------------------------------------------------------
#region Public Properties
private ContainerVisual InternalVisual
{
get
{
if (_internalVisual == null)
{
_internalVisual = new ContainerVisual();
AddVisualChild(_internalVisual);
}
return _internalVisual;
}
}
private UIElement InternalChild
{
get
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) return vc[0] as UIElement;
else return null;
}
set
{
VisualCollection vc = InternalVisual.Children;
if (vc.Count != 0) vc.Clear();
vc.Add(value);
}
}
private Transform InternalTransform
{
get
{
return InternalVisual.Transform;
}
set
{
InternalVisual.Transform = value;
}
}
/// <summary>
/// The single child of a <see cref="Viewbox" />
/// </summary>
public override UIElement Child
{
//everything is the same as on Decorator, the only difference is to insert intermediate Visual to
//specify scaling transform
get
{
return InternalChild;
}
set
{
UIElement old = InternalChild;
if (old != value)
{
//need to remove old element from logical tree
RemoveLogicalChild(old);
if (value != null)
{
AddLogicalChild(value);
}
InternalChild = value;
InvalidateMeasure();
}
}
}
/// <summary>
/// Returns the Visual children count.
/// </summary>
protected override int VisualChildrenCount
{
get { return 1; /* Always have internal container visual */ }
}
/// <summary>
/// Returns the child at the specified index.
/// </summary>
protected override Visual GetVisualChild(int index)
{
if (index != 0)
{
throw new ArgumentOutOfRangeException(/*"index", index, SR.Get(SRID.Visual_ArgumentOutOfRange)*/);
}
return InternalVisual;
}
/// <summary>
/// Returns enumerator to logical children.
/// </summary>
protected override IEnumerator LogicalChildren
{
get
{
if (InternalChild == null)
{
return EmptyEnumerator.Instance;
}
return new SingleChildEnumerator(InternalChild);
}
}
/// <summary>
/// Gets/Sets the Stretch mode of the Viewbox, which determines how the content will be
/// fit into the Viewbox's space.
///
/// </summary>
/// <seealso cref="Viewbox.StretchProperty" />
/// <seealso cref="Stretch" />
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
#endregion Public Properties
//-------------------------------------------------------------------
//
// Protected Methods
//
//-------------------------------------------------------------------
#region Protected Methods
/// <summary>
/// Updates DesiredSize of the Viewbox. Called by parent UIElement. This is the first pass of layout.
/// </summary>
/// <remarks>
/// Viewbox measures it's child at an infinite constraint; it allows the child to be however large it so desires.
/// The child's returned size will be used as it's natural size for scaling to Viewbox's size during Arrange.
/// </remarks>
/// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
/// <returns>The Decorator's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
var child = InternalChild;
var parentSize = new Size();
if (child != null)
{
// Initialize child constraint to infinity. We need to get a "natural" size for the child in absence of constraint.
// Note that an author *can* impose a constraint on a child by using Height/Width, &c... properties
var infiniteConstraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
child.Measure(infiniteConstraint);
var childSize = child.DesiredSize;
var scalefac = ComputeScaleFactor(constraint, childSize, Stretch);
parentSize.Width = scalefac * childSize.Width;
parentSize.Height = scalefac * childSize.Height;
if (parentSize.Width > constraint.Width)
parentSize.Width = constraint.Width;
childSize = new Size(constraint.Width / scalefac, constraint.Height / scalefac);
child.Measure(childSize);
}
return parentSize;
}
/// <summary>
/// Viewbox always sets the child to its desired size. It then computes and applies a transformation
/// from that size to the space available: Viewbox's own input size less child margin.
///
/// Viewbox also calls arrange on its child.
/// </summary>
/// <param name="arrangeSize">Size in which Border will draw the borders/background and children.</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
var child = InternalChild;
if (child != null)
{
var childSize = child.DesiredSize;
// Compute scaling factors from arrange size and the measured child content size
var scalefac = ComputeScaleFactor(arrangeSize, childSize, Stretch);
InternalTransform = new ScaleTransform(scalefac, scalefac);
childSize = new Size(arrangeSize.Width / scalefac, arrangeSize.Height / scalefac);
// Arrange the child to the desired size
child.Arrange(new Rect(new Point(), childSize));
// return the size occupied by scaled child
arrangeSize.Width = scalefac * childSize.Width;
arrangeSize.Height = scalefac * childSize.Height;
}
return arrangeSize;
}
/// <summary>
/// This is a helper function that computes scale factors depending on a target size and a content size
/// </summary>
/// <param name="availableSize">Size into which the content is being fitted.</param>
/// <param name="contentSize">Size of the content, measured natively (unconstrained).</param>
/// <param name="stretch">Value of the Stretch property on the element.</param>
internal static double ComputeScaleFactor(Size availableSize, Size contentSize, Stretch stretch)
{
// Compute scaling factors to use for axes
var scale = 1.0;
var isConstrainedHeight = !Double.IsPositiveInfinity(availableSize.Height);
if (isConstrainedHeight)
{
// Compute scaling factors for both axes
scale = (DoubleUtil.IsZero(contentSize.Height)) ? 0.0 : availableSize.Height / contentSize.Height;
}
return scale;
}
#endregion Protected Methods
//-------------------------------------------------------------------
//
// Private Fields
//
//-------------------------------------------------------------------
#region Private Fields
private ContainerVisual _internalVisual;
#endregion
}
static class DoubleUtil
{
internal const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
internal const float FLT_MIN = 1.175494351e-38F; /* Number close to zero, where float.MinValue is -float.MaxValue */
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * DBL_EPSILON;
}
}
}