@@ -88,39 +88,102 @@ private static void ScalePropertyChanged(DependencyObject d, DependencyPropertyC
88
88
}
89
89
}
90
90
91
+ private bool IsPositiveRealNumber ( double value ) => ! double . IsNaN ( value ) && ! double . IsInfinity ( value ) && value > 0 ;
92
+
93
+ private Size _lastMeasuredSize ;
94
+
91
95
/// <inheritdoc/>
92
96
protected override Size MeasureOverride ( Size availableSize )
93
97
{
94
- return base . MeasureOverride ( CalculateConstrainedSize ( availableSize ) ) ;
98
+ CalculateConstrainedSize ( ref availableSize ) ;
99
+
100
+ _lastMeasuredSize = availableSize ;
101
+
102
+ // Call base.MeasureOverride so any child elements know what room there is to work with.
103
+ // Don't return this though. An image that hasn't loaded yet for example will request very little space.
104
+ base . MeasureOverride ( _lastMeasuredSize ) ;
105
+ return _lastMeasuredSize ;
95
106
}
96
107
108
+ //// Our Arrange pass should just use the value we calculated in Measure, so we don't have extra work to do (at least the ContentPresenter we use presently does it for us.)
109
+
97
110
/// <inheritdoc/>
98
111
protected override Size ArrangeOverride ( Size finalSize )
99
112
{
100
- return base . ArrangeOverride ( CalculateConstrainedSize ( finalSize ) ) ;
113
+ // Even though we requested in measure to be a specific size, that doesn't mean our parent
114
+ // panel respected that request. Grid for instance can by default Stretch and if you don't
115
+ // set Horizontal/VerticalAlignment on the control it won't constrain as we expect.
116
+ // We could also be in a StackPanel/ScrollViewer where it wants to provide as much space as possible.
117
+ // However, if we always re-calculate even if we are provided the proper finalSize, this can trigger
118
+ // multiple arrange passes and cause a rounding error in layout. Therefore, we only want to
119
+ // re-calculate if we think we will have a significant impact.
120
+ //// TODO: Not sure what good tolerance is here
121
+ if ( Math . Abs ( finalSize . Width - _lastMeasuredSize . Width ) > 1.5 ||
122
+ Math . Abs ( finalSize . Height - _lastMeasuredSize . Height ) > 1.5 )
123
+ {
124
+ CalculateConstrainedSize ( ref finalSize ) ;
125
+
126
+ // Copy again so if Arrange is re-triggered we won't re-calculate.
127
+ _lastMeasuredSize = finalSize ;
128
+ }
129
+
130
+ return base . ArrangeOverride ( finalSize ) ;
101
131
}
102
132
103
- private Size CalculateConstrainedSize ( Size initialSize )
133
+ private void CalculateConstrainedSize ( ref Size availableSize )
104
134
{
105
- var availableSize = new Size ( initialSize . Width * ScaleX , initialSize . Height * ScaleY ) ;
135
+ var hasWidth = IsPositiveRealNumber ( availableSize . Width ) ;
136
+ var hasHeight = IsPositiveRealNumber ( availableSize . Height ) ;
137
+
138
+ if ( ! hasWidth && ! hasHeight )
139
+ {
140
+ // We have infinite space, like a ScrollViewer with both scrolling directions
141
+ // Ask child how big they want to be first.
142
+ availableSize = base . MeasureOverride ( availableSize ) ;
143
+
144
+ hasWidth = IsPositiveRealNumber ( availableSize . Width ) ;
145
+ hasHeight = IsPositiveRealNumber ( availableSize . Height ) ;
146
+
147
+ if ( ! hasWidth && ! hasHeight )
148
+ {
149
+ // At this point we have no way to determine a constraint, the Panel won't do anything
150
+ // This should be rare? We don't really have a way to provide a warning here.
151
+ return ;
152
+ }
153
+ }
154
+
155
+ // Scale size first before we constrain aspect ratio
156
+ availableSize . Width *= ScaleX ;
157
+ availableSize . Height *= ScaleY ;
106
158
107
159
// If we don't have an Aspect Ratio, just return the scaled value.
108
160
if ( ReadLocalValue ( AspectRatioProperty ) == DependencyProperty . UnsetValue )
109
161
{
110
- return availableSize ;
162
+ return ;
111
163
}
112
164
113
165
// Calculate the Aspect Ratio constraint based on the newly scaled size.
114
166
var currentAspect = availableSize . Width / availableSize . Height ;
115
- var desiredAspect = AspectRatio . Value ;
116
167
117
- if ( currentAspect >= desiredAspect )
168
+ if ( ! hasWidth )
169
+ {
170
+ // If available width is infinite, set width based on height
171
+ availableSize . Width = availableSize . Height * AspectRatio ;
172
+ }
173
+ else if ( ! hasHeight )
174
+ {
175
+ // If avalable height is infinite, set height based on width
176
+ availableSize . Height = availableSize . Width / AspectRatio ;
177
+ }
178
+ else if ( currentAspect > AspectRatio )
118
179
{
119
- return new Size ( availableSize . Height * desiredAspect , availableSize . Height ) ;
180
+ // If the container aspect ratio is wider than our aspect ratio, set width based on height
181
+ availableSize . Width = availableSize . Height * AspectRatio ;
120
182
}
121
183
else
122
184
{
123
- return new Size ( availableSize . Width , availableSize . Width / desiredAspect ) ;
185
+ // If the container aspect ratio is taller than our aspect ratio, set height based on width
186
+ availableSize . Height = availableSize . Width / AspectRatio ;
124
187
}
125
188
}
126
189
}
0 commit comments