diff --git a/readme.md b/readme.md
index 2b26cbdd1a..4fe76a2faf 100644
--- a/readme.md
+++ b/readme.md
@@ -18,7 +18,7 @@ Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a fl
## Getting Started
-Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (screenshot ). Now you can write code and markup that will work on multiple platforms!
+Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (screenshot ). Now you can write code and markup that will work on multiple platforms!
Avalonia is delivered via NuGet package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed))
@@ -52,7 +52,7 @@ Please read the [contribution guidelines](http://avaloniaui.net/contributing/con
### Contributors
This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
-
+
### Backers
diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs
index d28281e476..749f83c1ab 100644
--- a/samples/ControlCatalog/DecoratedWindow.xaml.cs
+++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs
@@ -20,7 +20,7 @@ namespace ControlCatalog
ctl.Cursor = new Cursor(cursor);
ctl.PointerPressed += delegate
{
- PlatformImpl.BeginResizeDrag(edge);
+ PlatformImpl?.BeginResizeDrag(edge);
};
}
@@ -29,7 +29,7 @@ namespace ControlCatalog
AvaloniaXamlLoader.Load(this);
this.FindControl("TitleBar").PointerPressed += delegate
{
- PlatformImpl.BeginMoveDrag();
+ PlatformImpl?.BeginMoveDrag();
};
SetupSide("Left", StandardCursorType.LeftSide, WindowEdge.West);
SetupSide("Right", StandardCursorType.RightSide, WindowEdge.East);
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
index 78f744cea0..041d91043a 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
@@ -110,7 +110,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
//Not supported
}
-
+ public void SetTopmost(bool value)
+ {
+ //Not supported
+ }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs
index ae70cacdba..ac7e97a4b1 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs
@@ -11,6 +11,7 @@ namespace Avalonia.Data.Core
{
internal abstract class ExpressionNode : ISubject
{
+ private static readonly object CacheInvalid = new object();
protected static readonly WeakReference UnsetReference =
new WeakReference(AvaloniaProperty.UnsetValue);
@@ -18,6 +19,8 @@ namespace Avalonia.Data.Core
private IDisposable _valueSubscription;
private IObserver _observer;
+ protected WeakReference LastValue { get; private set; }
+
public abstract string Description { get; }
public ExpressionNode Next { get; set; }
@@ -61,6 +64,7 @@ namespace Avalonia.Data.Core
{
_valueSubscription?.Dispose();
_valueSubscription = null;
+ LastValue = null;
nextSubscription?.Dispose();
_observer = null;
});
@@ -120,6 +124,7 @@ namespace Avalonia.Data.Core
if (notification == null)
{
+ LastValue = new WeakReference(value);
if (Next != null)
{
Next.Target = new WeakReference(value);
@@ -131,6 +136,7 @@ namespace Avalonia.Data.Core
}
else
{
+ LastValue = new WeakReference(notification.Value);
if (Next != null)
{
Next.Target = new WeakReference(notification.Value);
diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
index 7719f93a02..14bc09f5b7 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
@@ -154,7 +154,7 @@ namespace Avalonia.Data.Core
///
public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue)
{
- if (Leaf is ISettableNode settable)
+ if (Leaf is SettableNode settable)
{
var node = _node;
while (node != null)
@@ -188,7 +188,7 @@ namespace Avalonia.Data.Core
/// Gets the type of the expression result or null if the expression could not be
/// evaluated.
///
- public Type ResultType => (Leaf as ISettableNode)?.PropertyType;
+ public Type ResultType => (Leaf as SettableNode)?.PropertyType;
///
/// Gets the leaf node.
diff --git a/src/Avalonia.Base/Data/Core/ISettableNode.cs b/src/Avalonia.Base/Data/Core/ISettableNode.cs
deleted file mode 100644
index 7788407833..0000000000
--- a/src/Avalonia.Base/Data/Core/ISettableNode.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Avalonia.Data;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Avalonia.Data.Core
-{
- interface ISettableNode
- {
- bool SetTargetValue(object value, BindingPriority priority);
- Type PropertyType { get; }
- }
-}
diff --git a/src/Avalonia.Base/Data/Core/IndexerNode.cs b/src/Avalonia.Base/Data/Core/IndexerNode.cs
index 47e82fa2d3..633d3558ee 100644
--- a/src/Avalonia.Base/Data/Core/IndexerNode.cs
+++ b/src/Avalonia.Base/Data/Core/IndexerNode.cs
@@ -15,7 +15,7 @@ using Avalonia.Data;
namespace Avalonia.Data.Core
{
- internal class IndexerNode : ExpressionNode, ISettableNode
+ internal class IndexerNode : SettableNode
{
public IndexerNode(IList arguments)
{
@@ -52,7 +52,7 @@ namespace Avalonia.Data.Core
return Observable.Merge(inputs).StartWith(GetValue(target));
}
- public bool SetTargetValue(object value, BindingPriority priority)
+ protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
var typeInfo = Target.Target.GetType().GetTypeInfo();
var list = Target.Target as IList;
@@ -154,7 +154,7 @@ namespace Avalonia.Data.Core
public IList Arguments { get; }
- public Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType;
+ public override Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType;
private object GetValue(object target)
{
diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
index 4dbff4602f..9d657b3144 100644
--- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
+++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
@@ -10,7 +10,7 @@ using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core
{
- internal class PropertyAccessorNode : ExpressionNode, ISettableNode
+ internal class PropertyAccessorNode : SettableNode
{
private readonly bool _enableValidation;
private IPropertyAccessor _accessor;
@@ -23,13 +23,17 @@ namespace Avalonia.Data.Core
public override string Description => PropertyName;
public string PropertyName { get; }
- public Type PropertyType => _accessor?.PropertyType;
+ public override Type PropertyType => _accessor?.PropertyType;
- public bool SetTargetValue(object value, BindingPriority priority)
+ protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
if (_accessor != null)
{
- try { return _accessor.SetValue(value, priority); } catch { }
+ try
+ {
+ return _accessor.SetValue(value, priority);
+ }
+ catch { }
}
return false;
@@ -56,7 +60,10 @@ namespace Avalonia.Data.Core
() =>
{
_accessor = accessor;
- return Disposable.Create(() => _accessor = null);
+ return Disposable.Create(() =>
+ {
+ _accessor = null;
+ });
},
_ => accessor);
}
diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs
new file mode 100644
index 0000000000..092cdbe48f
--- /dev/null
+++ b/src/Avalonia.Base/Data/Core/SettableNode.cs
@@ -0,0 +1,38 @@
+using Avalonia.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Data.Core
+{
+ internal abstract class SettableNode : ExpressionNode
+ {
+ public bool SetTargetValue(object value, BindingPriority priority)
+ {
+ if (ShouldNotSet(value))
+ {
+ return true;
+ }
+ return SetTargetValueCore(value, priority);
+ }
+
+ private bool ShouldNotSet(object value)
+ {
+ if (PropertyType == null)
+ {
+ return false;
+ }
+ if (PropertyType.IsValueType)
+ {
+ return LastValue?.Target != null && LastValue.Target.Equals(value);
+ }
+ return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
+ }
+
+ protected abstract bool SetTargetValueCore(object value, BindingPriority priority);
+
+ public abstract Type PropertyType { get; }
+ }
+}
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index 351cbf5520..96fb9be8ac 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -2150,7 +2150,7 @@ namespace Avalonia.Controls
}
// Update the view
- if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace)
+ if ((e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) && e.OldItems != null)
{
for (int index = 0; index < e.OldItems.Count; index++)
{
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 59281c5ad0..029e4dadc8 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -769,7 +769,7 @@ namespace Avalonia.Controls
}
private static void UpdateDisplayDate(Calendar c, DateTime addedDate, DateTime removedDate)
{
- Debug.Assert(c != null, "c should not be null!");
+ Contract.Requires(c != null);
// If DisplayDate < DisplayDateStart, DisplayDate = DisplayDateStart
if (DateTime.Compare(addedDate, c.DisplayDateRangeStart) < 0)
@@ -1016,8 +1016,6 @@ namespace Avalonia.Controls
internal CalendarDayButton FindDayButtonFromDay(DateTime day)
{
- CalendarDayButton b;
- DateTime? d;
CalendarItem monthControl = MonthControl;
// REMOVE_RTM: should be updated if we support MultiCalendar
@@ -1028,14 +1026,16 @@ namespace Avalonia.Controls
{
for (int childIndex = ColumnsPerMonth; childIndex < count; childIndex++)
{
- b = monthControl.MonthView.Children[childIndex] as CalendarDayButton;
- d = b.DataContext as DateTime?;
-
- if (d.HasValue)
+ if (monthControl.MonthView.Children[childIndex] is CalendarDayButton b)
{
- if (DateTimeHelper.CompareDays(d.Value, day) == 0)
+ var d = b.DataContext as DateTime?;
+
+ if (d.HasValue)
{
- return b;
+ if (DateTimeHelper.CompareDays(d.Value, day) == 0)
+ {
+ return b;
+ }
}
}
}
@@ -1044,20 +1044,6 @@ namespace Avalonia.Controls
return null;
}
- private void Calendar_SizeChanged(object sender, EventArgs e)
- {
- Debug.Assert(sender is Calendar, "The sender should be a Calendar!");
-
- var size = Bounds.Size;
- RectangleGeometry rg = new RectangleGeometry();
- rg.Rect = new Rect(0, 0, size.Width, size.Height);
-
- if (Root != null)
- {
- Root.Clip = rg;
- }
- }
-
private void OnSelectedMonthChanged(DateTime? selectedMonth)
{
if (selectedMonth.HasValue)
@@ -1090,7 +1076,6 @@ namespace Avalonia.Controls
internal void ResetStates()
{
- CalendarDayButton d;
CalendarItem monthControl = MonthControl;
int count = RowsPerMonth * ColumnsPerMonth;
if (monthControl != null)
@@ -1099,7 +1084,7 @@ namespace Avalonia.Controls
{
for (int childIndex = ColumnsPerMonth; childIndex < count; childIndex++)
{
- d = monthControl.MonthView.Children[childIndex] as CalendarDayButton;
+ var d = (CalendarDayButton)monthControl.MonthView.Children[childIndex];
d.IgnoreMouseOverState();
}
}
@@ -1190,8 +1175,6 @@ namespace Avalonia.Controls
if (HoverEnd != null && HoverStart != null)
{
int startIndex, endIndex, i;
- CalendarDayButton b;
- DateTime? d;
CalendarItem monthControl = MonthControl;
// This assumes a contiguous set of dates:
@@ -1201,18 +1184,20 @@ namespace Avalonia.Controls
for (i = startIndex; i <= endIndex; i++)
{
- b = monthControl.MonthView.Children[i] as CalendarDayButton;
- b.IsSelected = true;
- d = b.DataContext as DateTime?;
-
- if (d.HasValue && DateTimeHelper.CompareDays(HoverEnd.Value, d.Value) == 0)
+ if (monthControl.MonthView.Children[i] is CalendarDayButton b)
{
- if (FocusButton != null)
+ b.IsSelected = true;
+ var d = b.DataContext as DateTime?;
+
+ if (d.HasValue && DateTimeHelper.CompareDays(HoverEnd.Value, d.Value) == 0)
{
- FocusButton.IsCurrent = false;
+ if (FocusButton != null)
+ {
+ FocusButton.IsCurrent = false;
+ }
+ b.IsCurrent = HasFocusInternal;
+ FocusButton = b;
}
- b.IsCurrent = HasFocusInternal;
- FocusButton = b;
}
}
}
@@ -1228,8 +1213,6 @@ namespace Avalonia.Controls
if (HoverEnd != null && HoverStart != null)
{
CalendarItem monthControl = MonthControl;
- CalendarDayButton b;
- DateTime? d;
if (HoverEndIndex != null && HoverStartIndex != null)
{
@@ -1240,15 +1223,17 @@ namespace Avalonia.Controls
{
for (i = startIndex; i <= endIndex; i++)
{
- b = monthControl.MonthView.Children[i] as CalendarDayButton;
- d = b.DataContext as DateTime?;
-
- if (d.HasValue)
+ if (monthControl.MonthView.Children[i] is CalendarDayButton b)
{
- if (!SelectedDates.Contains(d.Value))
+ var d = b.DataContext as DateTime?;
+
+ if (d.HasValue)
{
- b.IsSelected = false;
- }
+ if (!SelectedDates.Contains(d.Value))
+ {
+ b.IsSelected = false;
+ }
+ }
}
}
}
@@ -1257,7 +1242,7 @@ namespace Avalonia.Controls
// It is SingleRange
for (i = startIndex; i <= endIndex; i++)
{
- (monthControl.MonthView.Children[i] as CalendarDayButton).IsSelected = false;
+ ((CalendarDayButton)monthControl.MonthView.Children[i]).IsSelected = false;
}
}
}
@@ -1628,16 +1613,15 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
- internal void Calendar_KeyDown(object sender, KeyEventArgs e)
- {
- Calendar c = sender as Calendar;
- Debug.Assert(c != null, "c should not be null!");
- if (!e.Handled && c.IsEnabled)
+ internal void Calendar_KeyDown(KeyEventArgs e)
+ {
+ if (!e.Handled && IsEnabled)
{
e.Handled = ProcessCalendarKey(e);
}
}
+
internal bool ProcessCalendarKey(KeyEventArgs e)
{
if (DisplayMode == CalendarMode.Month)
@@ -1976,7 +1960,7 @@ namespace Avalonia.Controls
}
}
}
- private void Calendar_KeyUp(object sender, KeyEventArgs e)
+ private void Calendar_KeyUp(KeyEventArgs e)
{
if (!e.Handled && (e.Key == Key.LeftShift || e.Key == Key.RightShift))
{
@@ -2083,6 +2067,9 @@ namespace Avalonia.Controls
DisplayDateProperty.Changed.AddClassHandler(x => x.OnDisplayDateChanged);
DisplayDateStartProperty.Changed.AddClassHandler(x => x.OnDisplayDateStartChanged);
DisplayDateEndProperty.Changed.AddClassHandler(x => x.OnDisplayDateEndChanged);
+ KeyDownEvent.AddClassHandler(x => x.Calendar_KeyDown);
+ KeyUpEvent.AddClassHandler(x => x.Calendar_KeyUp);
+
}
///
@@ -2122,10 +2109,6 @@ namespace Avalonia.Controls
month.Owner = this;
}
}
-
- LayoutUpdated += Calendar_SizeChanged;
- KeyDown += Calendar_KeyDown;
- KeyUp += Calendar_KeyUp;
}
}
diff --git a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs
index 273cda8c5b..718cc7142b 100644
--- a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs
@@ -66,7 +66,7 @@ namespace Avalonia.Controls
/// Inherited code: Requires comment 2.
internal bool ContainsAny(CalendarDateRange range)
{
- Debug.Assert(range != null, "range should not be null!");
+ Contract.Requires(range != null);
int start = DateTime.Compare(Start, range.Start);
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index 3432fa549d..b0cbd0be53 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -517,7 +517,7 @@ namespace Avalonia.Controls.Primitives
for (int childIndex = Calendar.ColumnsPerMonth; childIndex < count; childIndex++)
{
CalendarDayButton childButton = MonthView.Children[childIndex] as CalendarDayButton;
- Debug.Assert(childButton != null, "childButton should not be null!");
+ Contract.Requires(childButton != null);
childButton.Index = childIndex;
SetButtonState(childButton, dateToAdd);
@@ -554,7 +554,7 @@ namespace Avalonia.Controls.Primitives
for (int i = childIndex; i < count; i++)
{
childButton = MonthView.Children[i] as CalendarDayButton;
- Debug.Assert(childButton != null, "childButton should not be null!");
+ Contract.Requires(childButton != null);
// button needs a content to occupy the necessary space
// for the content presenter
childButton.Content = i.ToString(DateTimeHelper.GetCurrentDateFormat());
@@ -650,7 +650,7 @@ namespace Avalonia.Controls.Primitives
foreach (object child in YearView.Children)
{
CalendarButton childButton = child as CalendarButton;
- Debug.Assert(childButton != null, "childButton should not be null!");
+ Contract.Requires(childButton != null);
// There should be no time component. Time is 12:00 AM
DateTime day = new DateTime(_currentMonth.Year, count + 1, 1);
childButton.DataContext = day;
@@ -746,7 +746,7 @@ namespace Avalonia.Controls.Primitives
foreach (object child in YearView.Children)
{
CalendarButton childButton = child as CalendarButton;
- Debug.Assert(childButton != null, "childButton should not be null!");
+ Contract.Requires(childButton != null);
year = decade + count;
if (year <= DateTime.MaxValue.Year && year >= DateTime.MinValue.Year)
@@ -826,7 +826,7 @@ namespace Avalonia.Controls.Primitives
{
Owner.Focus();
}
- Button b = sender as Button;
+ Button b = (Button)sender;
DateTime d;
if (b.IsEnabled)
@@ -863,7 +863,7 @@ namespace Avalonia.Controls.Primitives
Owner.Focus();
}
- Button b = sender as Button;
+ Button b = (Button)sender;
if (b.IsEnabled)
{
Owner.OnPreviousClick();
@@ -878,7 +878,7 @@ namespace Avalonia.Controls.Primitives
{
Owner.Focus();
}
- Button b = sender as Button;
+ Button b = (Button)sender;
if (b.IsEnabled)
{
@@ -891,8 +891,7 @@ namespace Avalonia.Controls.Primitives
{
if (Owner != null)
{
- CalendarDayButton b = sender as CalendarDayButton;
- if (_isMouseLeftButtonDown && b != null && b.IsEnabled && !b.IsBlackout)
+ if (_isMouseLeftButtonDown && sender is CalendarDayButton b && b.IsEnabled && !b.IsBlackout)
{
// Update the states of all buttons to be selected starting
// from HoverStart to b
@@ -918,7 +917,7 @@ namespace Avalonia.Controls.Primitives
Debug.Assert(b.DataContext != null, "The DataContext should not be null!");
Owner.UnHighlightDays();
Owner.HoverEndIndex = b.Index;
- Owner.HoverEnd = (DateTime)b.DataContext;
+ Owner.HoverEnd = (DateTime?)b.DataContext;
// Update the States of the buttons
Owner.HighlightDays();
return;
@@ -931,7 +930,7 @@ namespace Avalonia.Controls.Primitives
{
if (_isMouseLeftButtonDown)
{
- CalendarDayButton b = sender as CalendarDayButton;
+ CalendarDayButton b = (CalendarDayButton)sender;
// The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b)
e.Device.Capture(null);
@@ -973,7 +972,7 @@ namespace Avalonia.Controls.Primitives
if (b.IsEnabled && !b.IsBlackout)
{
DateTime selectedDate = (DateTime)b.DataContext;
- Debug.Assert(selectedDate != null, "selectedDate should not be null!");
+ Contract.Requires(selectedDate != null);
_isMouseLeftButtonDown = true;
// null check is added for unit tests
if (e != null)
@@ -1149,7 +1148,7 @@ namespace Avalonia.Controls.Primitives
if (_isControlPressed && Owner.SelectionMode == CalendarSelectionMode.MultipleRange)
{
CalendarDayButton b = sender as CalendarDayButton;
- Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
+ Contract.Requires(b != null);
if (b.IsSelected)
{
@@ -1169,7 +1168,7 @@ namespace Avalonia.Controls.Primitives
private void Month_CalendarButtonMouseDown(object sender, PointerPressedEventArgs e)
{
CalendarButton b = sender as CalendarButton;
- Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
+ Contract.Requires(b != null);
_isMouseLeftButtonDownYearView = true;
@@ -1208,7 +1207,7 @@ namespace Avalonia.Controls.Primitives
if (_isMouseLeftButtonDownYearView)
{
CalendarButton b = sender as CalendarButton;
- Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
+ Contract.Requires(b != null);
UpdateYearViewSelection(b);
}
}
@@ -1217,7 +1216,7 @@ namespace Avalonia.Controls.Primitives
{
if (_isMouseLeftButtonDownYearView)
{
- CalendarButton b = sender as CalendarButton;
+ CalendarButton b = (CalendarButton)sender;
// The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b)
e.Device.Capture(null);
diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs
index 418ef50b2c..08608ad359 100644
--- a/src/Avalonia.Controls/Calendar/DatePicker.cs
+++ b/src/Avalonia.Controls/Calendar/DatePicker.cs
@@ -842,7 +842,7 @@ namespace Avalonia.Controls
private void Calendar_KeyDown(object sender, KeyEventArgs e)
{
Calendar c = sender as Calendar;
- Debug.Assert(c != null, "The Calendar should not be null!");
+ Contract.Requires(c != null);
if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month)
{
diff --git a/src/Avalonia.Controls/DropDownItem.cs b/src/Avalonia.Controls/DropDownItem.cs
index 3fd80c4562..fb465e93ec 100644
--- a/src/Avalonia.Controls/DropDownItem.cs
+++ b/src/Avalonia.Controls/DropDownItem.cs
@@ -22,14 +22,13 @@ namespace Avalonia.Controls
static DropDownItem()
{
FocusableProperty.OverrideDefaultValue(true);
- IsFocusedProperty.Changed.Subscribe(x =>
- {
- var sender = x.Sender as IControl;
+ }
- if (sender != null)
- {
- ((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue);
- }
+ public DropDownItem()
+ {
+ this.GetObservable(DropDownItem.IsFocusedProperty).Subscribe(focused =>
+ {
+ PseudoClasses.Set(":selected", focused);
});
}
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
index cd2da692ba..4db16c71a5 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
@@ -57,7 +57,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
public void Dispose()
{
- PlatformImpl.Dispose();
+ PlatformImpl?.Dispose();
}
}
}
diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
index 4f7ac82df7..9ba68f584e 100644
--- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
@@ -72,6 +72,11 @@ namespace Avalonia.Platform
///
void SetMinMaxSize(Size minSize, Size maxSize);
+ ///
+ /// Sets whether this window appears on top of all other windows
+ ///
+ void SetTopmost(bool value);
+
///
/// Gets platform specific display information
///
diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
index e136efe2a9..52e09aba45 100644
--- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs
+++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
@@ -62,7 +62,7 @@ namespace Avalonia.Platform
RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects);
var tl = root.GetSelfAndVisualAncestors().OfType().FirstOrDefault();
- tl.PlatformImpl.Input(rawEvent);
+ tl.PlatformImpl?.Input(rawEvent);
var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
UpdateCursor(root, effect);
diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
index 144174e371..e438843078 100644
--- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs
@@ -126,7 +126,21 @@ namespace Avalonia.Controls.Presenters
generator.Clear();
Panel.Children.RemoveAll(containers.Select(x => x.ContainerControl));
- MoveToPage(-1, SelectedIndex >= 0 ? SelectedIndex : 0);
+ var newIndex = SelectedIndex;
+
+ if(SelectedIndex < 0)
+ {
+ if(Items != null && Items.Count() > 0)
+ {
+ newIndex = 0;
+ }
+ else
+ {
+ newIndex = -1;
+ }
+ }
+
+ MoveToPage(-1, newIndex);
}
break;
}
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 656f3890cd..005717d681 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -70,6 +70,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty StaysOpenProperty =
AvaloniaProperty.Register(nameof(StaysOpen), true);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty TopmostProperty =
+ AvaloniaProperty.Register(nameof(Topmost));
+
private bool _isOpen;
private PopupRoot _popupRoot;
private TopLevel _topLevel;
@@ -84,6 +90,7 @@ namespace Avalonia.Controls.Primitives
IsHitTestVisibleProperty.OverrideDefaultValue(false);
ChildProperty.Changed.AddClassHandler(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler(x => x.IsOpenChanged);
+ TopmostProperty.Changed.AddClassHandler((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
}
///
@@ -194,6 +201,15 @@ namespace Avalonia.Controls.Primitives
set { SetValue(StaysOpenProperty, value); }
}
+ ///
+ /// Gets or sets whether this popup appears on top of all other windows
+ ///
+ public bool Topmost
+ {
+ get { return GetValue(TopmostProperty); }
+ set { SetValue(TopmostProperty, value); }
+ }
+
///
/// Gets the root of the popup window.
///
diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs
index 0057b15150..3ddcb06303 100644
--- a/src/Avalonia.Controls/Primitives/ScrollBar.cs
+++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs
@@ -6,9 +6,21 @@ using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Interactivity;
+using Avalonia.Input;
namespace Avalonia.Controls.Primitives
{
+ public class ScrollEventArgs : EventArgs
+ {
+ public ScrollEventArgs(ScrollEventType eventType, double newValue)
+ {
+ ScrollEventType = eventType;
+ NewValue = newValue;
+ }
+ public double NewValue { get; private set; }
+ public ScrollEventType ScrollEventType { get; private set; }
+ }
+
///
/// A scrollbar control.
///
@@ -44,6 +56,9 @@ namespace Avalonia.Controls.Primitives
{
PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
+
+ Thumb.DragDeltaEvent.AddClassHandler(o => o.OnThumbDragDelta, RoutingStrategies.Bubble);
+ Thumb.DragCompletedEvent.AddClassHandler(o => o.OnThumbDragComplete, RoutingStrategies.Bubble);
}
///
@@ -88,6 +103,8 @@ namespace Avalonia.Controls.Primitives
set { SetValue(OrientationProperty, value); }
}
+ public event EventHandler Scroll;
+
///
/// Calculates whether the scrollbar should be visible.
///
@@ -140,6 +157,8 @@ namespace Avalonia.Controls.Primitives
_pageUpButton = e.NameScope.Find("PART_PageUpButton");
_pageDownButton = e.NameScope.Find("PART_PageDownButton");
+
+
if (_lineUpButton != null)
{
_lineUpButton.Click += LineUpClick;
@@ -184,21 +203,39 @@ namespace Avalonia.Controls.Primitives
private void SmallDecrement()
{
Value = Math.Max(Value - SmallChange * ViewportSize, Minimum);
+ OnScroll(ScrollEventType.SmallDecrement);
}
private void SmallIncrement()
{
Value = Math.Min(Value + SmallChange * ViewportSize, Maximum);
+ OnScroll(ScrollEventType.SmallIncrement);
}
private void LargeDecrement()
{
Value = Math.Max(Value - LargeChange * ViewportSize, Minimum);
+ OnScroll(ScrollEventType.LargeDecrement);
}
private void LargeIncrement()
{
Value = Math.Min(Value + LargeChange * ViewportSize, Maximum);
+ OnScroll(ScrollEventType.LargeIncrement);
+ }
+
+ private void OnThumbDragDelta(VectorEventArgs e)
+ {
+ OnScroll(ScrollEventType.ThumbTrack);
+ }
+ private void OnThumbDragComplete(VectorEventArgs e)
+ {
+ OnScroll(ScrollEventType.EndScroll);
+ }
+
+ protected void OnScroll(ScrollEventType scrollEventType)
+ {
+ Scroll?.Invoke(this, new ScrollEventArgs(scrollEventType, Value));
}
}
}
diff --git a/src/Avalonia.Controls/Primitives/ScrollEventType.cs b/src/Avalonia.Controls/Primitives/ScrollEventType.cs
new file mode 100644
index 0000000000..ea639d82e0
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/ScrollEventType.cs
@@ -0,0 +1,53 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+namespace Avalonia.Controls.Primitives
+{
+ ///
+ /// Specifies the type of Avalonia.Controls.Primitives.ScrollBar.Scroll event
+ /// that occurred.
+ ///
+ public enum ScrollEventType
+ {
+ ///
+ /// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
+ /// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.SmallChange.
+ /// The Avalonia.Controls.Primitives.Thumb moved to the left for a horizontal
+ /// Avalonia.Controls.Primitives.ScrollBar or upward for a vertical Avalonia.Controls.Primitives.ScrollBar.
+ ///
+ SmallDecrement = 0,
+ ///
+ /// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
+ /// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.SmallChange.
+ /// The Avalonia.Controls.Primitives.Thumb moved to the right for a horizontal
+ /// Avalonia.Controls.Primitives.ScrollBar or downward for a vertical Avalonia.Controls.Primitives.ScrollBar.
+ ///
+ SmallIncrement = 1,
+ ///
+ /// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
+ /// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.LargeChange.
+ /// The Avalonia.Controls.Primitives.Thumb moved to the left for a horizontal
+ /// Avalonia.Controls.Primitives.ScrollBar or upward for a vertical Avalonia.Controls.Primitives.ScrollBar.
+ ///
+ LargeDecrement = 2,
+ ///
+ /// Specifies that the Avalonia.Controls.Primitives.Thumb moved a specified
+ /// distance, as determined by the value of Avalonia.Controls.Primitives.RangeBase.LargeChange.
+ /// The Avalonia.Controls.Primitives.Thumb moved to the right for a horizontal
+ /// Avalonia.Controls.Primitives.ScrollBar or downward for a vertical Avalonia.Controls.Primitives.ScrollBar.
+ ///
+ LargeIncrement = 3,
+ ///
+ /// The Avalonia.Controls.Primitives.Thumb was dragged and caused a Avalonia.UIElement.MouseMove
+ /// event. A Avalonia.Controls.Primitives.ScrollBar.Scroll event of this Avalonia.Controls.Primitives.ScrollEventType
+ /// may occur more than one time when the Avalonia.Controls.Primitives.Thumb
+ /// is dragged in the Avalonia.Controls.Primitives.ScrollBar.
+ ///
+ ThumbTrack = 4,
+ ///
+ /// Specifies that the Avalonia.Controls.Primitives.Thumb was dragged to a
+ /// new position and is now no longer being dragged by the user.
+ ///
+ EndScroll = 5
+ }
+}
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index 464e4188d2..10e964d014 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/src/Avalonia.Controls/ToolTip.cs
@@ -234,11 +234,12 @@ namespace Avalonia.Controls
{
Close();
- _popup = new PopupRoot { Content = this };
+ _popup = new PopupRoot { Content = this, };
((ISetLogicalParent)_popup).SetParent(control);
_popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup,
GetHorizontalOffset(control), GetVerticalOffset(control));
_popup.Show();
+ _popup.SnapInsideScreenEdges();
}
private void Close()
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index c427df1c26..5d66bee2f8 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -38,6 +38,9 @@ namespace Avalonia.Controls
o => o.Owner,
(o, v) => o.Owner = v);
+ public static readonly StyledProperty TopmostProperty =
+ AvaloniaProperty.Register(nameof(Topmost));
+
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
@@ -52,6 +55,8 @@ namespace Avalonia.Controls
MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
+
+ TopmostProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue));
}
public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current)
@@ -124,6 +129,15 @@ namespace Avalonia.Controls
set { SetAndRaise(OwnerProperty, ref _owner, value); }
}
+ ///
+ /// Gets or sets whether this window appears on top of all other windows
+ ///
+ public bool Topmost
+ {
+ get { return GetValue(TopmostProperty); }
+ set { SetValue(TopmostProperty, value); }
+ }
+
///
/// Activates the window.
///
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index ef16d06b60..d9a30f0d29 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -102,5 +102,9 @@ namespace Avalonia.DesignerSupport.Remote
public void CanResize(bool value)
{
}
+
+ public void SetTopmost(bool value)
+ {
+ }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index e749d10468..43f0e0a95e 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -104,6 +104,10 @@ namespace Avalonia.DesignerSupport.Remote
public void CanResize(bool value)
{
}
+
+ public void SetTopmost(bool value)
+ {
+ }
}
class ClipboardStub : IClipboard
diff --git a/src/Avalonia.Themes.Default/Calendar.xaml b/src/Avalonia.Themes.Default/Calendar.xaml
index 5e4306c217..61b1224424 100644
--- a/src/Avalonia.Themes.Default/Calendar.xaml
+++ b/src/Avalonia.Themes.Default/Calendar.xaml
@@ -15,9 +15,10 @@
+ HorizontalAlignment="Center"
+ ClipToBounds="True">
-
/// Implicit conversion of string to FontFamily
///
- ///
- public static implicit operator FontFamily(string fontFamily)
+ ///
+ public static implicit operator FontFamily(string s)
{
- return new FontFamily(fontFamily);
+ return Parse(s);
}
///
diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
index 1adaf9f4e1..9cc6ba6901 100644
--- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
+++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
@@ -261,10 +261,13 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_unmaximize(GtkWindow window);
-
+
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_close(GtkWindow window);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+ public delegate void gtk_window_set_keep_above(GtkWindow gtkWindow, bool setting);
+
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
@@ -472,6 +475,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_maximize GtkWindowMaximize;
public static D.gtk_window_unmaximize GtkWindowUnmaximize;
public static D.gtk_window_close GtkWindowClose;
+ public static D.gtk_window_set_keep_above GtkWindowSetKeepAbove;
public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag;
public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
public static D.gdk_event_request_motions GdkEventRequestMotions;
diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
index 94537d3475..8a880fd306 100644
--- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
+++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
@@ -416,6 +416,8 @@ namespace Avalonia.Gtk3
public void Hide() => Native.GtkWidgetHide(GtkWidget);
+ public void SetTopmost(bool value) => Native.GtkWindowSetKeepAbove(GtkWidget, value);
+
void GetGlobalPointer(out int x, out int y)
{
int mask;
diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs
index bae34db6f3..a0b754c229 100644
--- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs
+++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs
@@ -81,7 +81,7 @@ namespace Avalonia.Gtk3
public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value);
public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value);
-
+
class EmptyDisposable : IDisposable
{
diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs
index f6f62dcd1d..a3fa6880ac 100644
--- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs
+++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs
@@ -10,6 +10,7 @@ using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Metadata;
+using JetBrains.Annotations;
namespace Avalonia.Data
{
@@ -65,7 +66,7 @@ namespace Avalonia.Data
var children = Bindings.Select(x => x.Initiate(target, null));
var input = children.Select(x => x.Subject).CombineLatest().Select(x => ConvertValue(x, targetType));
var mode = Mode == BindingMode.Default ?
- targetProperty.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
+ targetProperty?.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
switch (mode)
{
diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
index 8cbc6cbdd8..89cef59b53 100644
--- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
+++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
@@ -123,6 +123,7 @@ namespace Avalonia.MonoMac
public void Hide() => Window?.OrderOut(Window);
+ public void SetTopmost(bool value) => Window.Level = value ? NSWindowLevel.Floating : NSWindowLevel.Normal;
public void BeginMoveDrag()
{
diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
index 00f0a48a7b..13dcd9669d 100644
--- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
@@ -560,13 +560,11 @@ namespace Avalonia.Skia
}
measured = LineBreak(Text, curOff, length, _paint, constraint, out trailingnumber);
-
AvaloniaFormattedTextLine line = new AvaloniaFormattedTextLine();
line.TextLength = measured;
-
+ line.Start = curOff;
subString = Text.Substring(line.Start, line.TextLength);
lineWidth = _paint.MeasureText(subString);
- line.Start = curOff;
line.Length = measured - trailingnumber;
line.Width = lineWidth;
line.Height = _lineHeight;
@@ -575,10 +573,33 @@ namespace Avalonia.Skia
_skiaLines.Add(line);
curY += _lineHeight;
-
curY += mLeading;
-
curOff += measured;
+
+ //if this is the last line and there are trailing newline characters then
+ //insert a additional line
+ if (curOff >= length)
+ {
+ var subStringMinusNewlines = subString.TrimEnd('\n', '\r');
+ var lengthDiff = subString.Length - subStringMinusNewlines.Length;
+ if (lengthDiff > 0)
+ {
+ AvaloniaFormattedTextLine lastLine = new AvaloniaFormattedTextLine();
+ lastLine.TextLength = lengthDiff;
+ lastLine.Start = curOff - lengthDiff;
+ var lastLineSubString = Text.Substring(line.Start, line.TextLength);
+ var lastLineWidth = _paint.MeasureText(lastLineSubString);
+ lastLine.Length = 0;
+ lastLine.Width = lastLineWidth;
+ lastLine.Height = _lineHeight;
+ lastLine.Top = curY;
+
+ _skiaLines.Add(lastLine);
+
+ curY += _lineHeight;
+ curY += mLeading;
+ }
+ }
}
// Now convert to Avalonia data formats
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 86dcec410b..daaee9636e 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -78,6 +78,14 @@ namespace Avalonia.Win32.Interop
SWP_RESIZE = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER
}
+ public static class WindowPosZOrder
+ {
+ public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
+ public static readonly IntPtr HWND_TOP = new IntPtr(0);
+ public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
+ public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
+ }
+
public enum SizeCommand
{
Restored,
diff --git a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs
index e08fecbbd9..9b6bf57e92 100644
--- a/src/Windows/Avalonia.Win32/SystemDialogImpl.cs
+++ b/src/Windows/Avalonia.Win32/SystemDialogImpl.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Win32
var fileBuffer = new char[256];
dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length);
- string userSelectedExt = null;
+ string userSelectedExt = string.Empty;
var title = ToChars(dialog.Title);
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 159c8386b6..9f67f97252 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -32,6 +32,7 @@ namespace Avalonia.Win32
private bool _trackingMouse;
private bool _decorated = true;
private bool _resizable = true;
+ private bool _topmost = false;
private double _scaling = 1;
private WindowState _showWindowState;
private WindowState _lastWindowState;
@@ -841,7 +842,7 @@ namespace Avalonia.Win32
var cx = Math.Abs(monitorInfo.rcWork.right - x);
var cy = Math.Abs(monitorInfo.rcWork.bottom - y);
- SetWindowPos(_hwnd, new IntPtr(-2), x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW);
+ SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW);
}
}
}
@@ -904,5 +905,21 @@ namespace Avalonia.Win32
_resizable = value;
}
+
+ public void SetTopmost(bool value)
+ {
+ if (value == _topmost)
+ {
+ return;
+ }
+
+ IntPtr hWndInsertAfter = value ? WindowPosZOrder.HWND_TOPMOST : WindowPosZOrder.HWND_NOTOPMOST;
+ UnmanagedMethods.SetWindowPos(_hwnd,
+ hWndInsertAfter,
+ 0, 0, 0, 0,
+ SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE);
+
+ _topmost = value;
+ }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index 02fb1f11ad..4638aa84a5 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
@@ -457,6 +457,28 @@ namespace Avalonia.Base.UnitTests
Assert.True(target.IsAnimating(Class1.FooProperty));
}
+ [Fact]
+ public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation()
+ {
+ var target = new Class1();
+ var source = new TestTwoWayBindingViewModel();
+
+ target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source });
+
+ Assert.False(source.SetterCalled);
+ }
+
+ [Fact]
+ public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation_Indexer()
+ {
+ var target = new Class1();
+ var source = new TestTwoWayBindingViewModel();
+
+ target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source });
+
+ Assert.False(source.SetterCalled);
+ }
+
///
/// Returns an observable that returns a single value but does not complete.
///
@@ -545,5 +567,32 @@ namespace Avalonia.Base.UnitTests
}
}
}
+
+ private class TestTwoWayBindingViewModel
+ {
+ private double _value;
+
+ public double Value
+ {
+ get => _value;
+ set
+ {
+ _value = value;
+ SetterCalled = true;
+ }
+ }
+
+ public double this[int index]
+ {
+ get => _value;
+ set
+ {
+ _value = value;
+ SetterCalled = true;
+ }
+ }
+
+ public bool SetterCalled { get; private set; }
+ }
}
}
\ No newline at end of file
diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
index df61698209..f36e5864f6 100644
--- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
@@ -170,6 +170,41 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal("Bar", ((TextBlock)child).Text);
}
+ [Fact]
+ public void Selected_Index_Changes_To_When_Items_Assigned_Null()
+ {
+ var items = new ObservableCollection
+ {
+ "Foo",
+ "Bar",
+ "FooBar"
+ };
+
+ var target = new Carousel
+ {
+ Template = new FuncControlTemplate(CreateTemplate),
+ Items = items,
+ IsVirtualized = false
+ };
+
+ target.ApplyTemplate();
+ target.Presenter.ApplyTemplate();
+
+ Assert.Single(target.GetLogicalChildren());
+
+ var child = target.GetLogicalChildren().Single();
+
+ Assert.IsType(child);
+ Assert.Equal("Foo", ((TextBlock)child).Text);
+
+ target.Items = null;
+
+ var numChildren = target.GetLogicalChildren().Count();
+
+ Assert.Equal(0, numChildren);
+ Assert.Equal(-1, target.SelectedIndex);
+ }
+
[Fact]
public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex()
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs
index 0af4d791c7..672b5c608b 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/ScrollBarTests.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
+using Avalonia.Input;
using Avalonia.Media;
using Xunit;
@@ -59,6 +60,64 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(50, target.Value);
}
+ [Fact]
+ public void Thumb_DragDelta_Event_Should_Raise_Scroll_Event()
+ {
+ var target = new ScrollBar
+ {
+ Template = new FuncControlTemplate(Template),
+ };
+
+ target.ApplyTemplate();
+
+ var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
+
+ var raisedEvent = Assert.Raises(
+ handler => target.Scroll += handler,
+ handler => target.Scroll -= handler,
+ () =>
+ {
+ var ev = new VectorEventArgs
+ {
+ RoutedEvent = Thumb.DragDeltaEvent,
+ Vector = new Vector(0, 0)
+ };
+
+ track.Thumb.RaiseEvent(ev);
+ });
+
+ Assert.Equal(ScrollEventType.ThumbTrack, raisedEvent.Arguments.ScrollEventType);
+ }
+
+ [Fact]
+ public void Thumb_DragComplete_Event_Should_Raise_Scroll_Event()
+ {
+ var target = new ScrollBar
+ {
+ Template = new FuncControlTemplate(Template),
+ };
+
+ target.ApplyTemplate();
+
+ var track = (Track)target.GetTemplateChildren().First(x => x.Name == "track");
+
+ var raisedEvent = Assert.Raises(
+ handler => target.Scroll += handler,
+ handler => target.Scroll -= handler,
+ () =>
+ {
+ var ev = new VectorEventArgs
+ {
+ RoutedEvent = Thumb.DragCompletedEvent,
+ Vector = new Vector(0, 0)
+ };
+
+ track.Thumb.RaiseEvent(ev);
+ });
+
+ Assert.Equal(ScrollEventType.EndScroll, raisedEvent.Arguments.ScrollEventType);
+ }
+
[Fact]
public void ScrollBar_Can_AutoHide()
{