Browse Source

Merge branch 'master' into feature/1279-combining-geometries

pull/6576/head
Steven Kirk 5 years ago
committed by GitHub
parent
commit
1dd4c6cb5f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 51
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  2. 49
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  3. 11
      src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
  4. 36
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  5. 15
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  6. 17
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  7. 22
      src/Avalonia.Controls/Primitives/Popup.cs
  8. 4
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  9. 7
      src/Avalonia.Visuals/Visual.cs
  10. 23
      src/Markup/Avalonia.Markup.Xaml/Extensions.cs
  11. 18
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  12. 23
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
  13. 1
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  14. 73
      tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs

51
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3039,6 +3039,12 @@ namespace Avalonia.Controls
}
}
//TODO: Ensure right button is checked for
internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
}
//TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
@ -4489,17 +4495,27 @@ namespace Avalonia.Controls
element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext);
if (element != null)
{
// Subscribe to the new element's events
element.Initialized += EditingElement_Initialized;
dataGridCell.Content = element;
if (element.IsInitialized)
{
PreparingCellForEditPrivate(element as Control);
}
else
{
// Subscribe to the new element's events
element.Initialized += EditingElement_Initialized;
}
}
}
else
{
// Generate Element and apply column style if available
element = dataGridColumn.GenerateElementInternal(dataGridCell, dataGridRow.DataContext);
dataGridCell.Content = element;
}
dataGridCell.Content = element;
}
private void PreparingCellForEditPrivate(Control editingElement)
@ -5711,6 +5727,35 @@ namespace Avalonia.Controls
VerticalScroll?.Invoke(sender, e);
}
//TODO: Ensure right button is checked for
private bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
{
Debug.Assert(slot >= 0);
if (shift || ctrl)
{
return true;
}
if (IsSlotOutOfBounds(slot))
{
return true;
}
if (GetRowSelection(slot))
{
return true;
}
// Unselect everything except the row that was clicked on
try
{
UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
}
finally
{
NoSelectionChangeCount--;
}
return true;
}
//TODO: Ensure left button is checked for
private bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
{

49
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@ -161,29 +161,42 @@ namespace Avalonia.Controls
private void DataGridCell_PointerPressed(PointerPressedEventArgs e)
{
// OwningGrid is null for TopLeftHeaderCell and TopRightHeaderCell because they have no OwningRow
if (OwningGrid != null)
if (OwningGrid == null)
{
OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
}
OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
// Do not handle PointerPressed with touch,
// so we can start scroll gesture on the same event.
if (e.Pointer.Type != PointerType.Touch)
{
OwningGrid.Focus();
e.Handled = handled;
}
if (OwningRow != null)
{
var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
// Do not handle PointerPressed with touch,
// so we can start scroll gesture on the same event.
if (e.Pointer.Type != PointerType.Touch)
{
e.Handled = handled;
}
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
}
}
}

11
src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs

@ -233,7 +233,7 @@ namespace Avalonia.Controls
else
{
editableCollectionView.EditItem(dataItem);
return editableCollectionView.IsEditingItem;
return editableCollectionView.IsEditingItem || editableCollectionView.IsAddingNew;
}
}
@ -314,7 +314,14 @@ namespace Avalonia.Controls
CommittingEdit = true;
try
{
editableCollectionView.CommitEdit();
if (editableCollectionView.IsAddingNew)
{
editableCollectionView.CommitNew();
}
else
{
editableCollectionView.CommitEdit();
}
}
finally
{

36
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -378,13 +378,13 @@ namespace Avalonia.Controls
}
}
}
}
}
internal Panel RootElement
{
get;
private set;
}
}
internal int Slot
{
@ -638,7 +638,7 @@ namespace Avalonia.Controls
PseudoClasses.Set(":editing", IsEditing);
PseudoClasses.Set(":invalid", !IsValid);
ApplyHeaderStatus();
}
}
}
//TODO Animation
@ -896,7 +896,7 @@ namespace Avalonia.Controls
_detailsElement.ContentHeight = _detailsDesiredHeight;
}
}
}
}
// Makes sure the _detailsDesiredHeight is initialized. We need to measure it to know what
// height we want to animate to. Subsequently, we just update that height in response to SizeChanged
@ -919,7 +919,7 @@ namespace Avalonia.Controls
//TODO Cleanup
double? _previousDetailsHeight = null;
//TODO Animation
private void DetailsContent_HeightChanged(double newValue)
{
@ -1022,7 +1022,7 @@ namespace Avalonia.Controls
}
}
}
internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight)
{
if (_detailsElement != null && AreDetailsVisible)
@ -1066,7 +1066,7 @@ namespace Avalonia.Controls
.Subscribe(DetailsContent_MarginChanged);
}
_detailsElement.Children.Add(_detailsContent);
}
}
@ -1090,6 +1090,28 @@ namespace Avalonia.Controls
}
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
if (change.Property == DataContextProperty)
{
var owner = OwningGrid;
if (owner != null && this.IsRecycled)
{
var columns = owner.ColumnsItemsInternal;
var nc = columns.Count;
for (int ci = 0; ci < nc; ci++)
{
if (columns[ci] is DataGridTemplateColumn column)
{
column.RefreshCellContent((Control)this.Cells[column.Index].Content, nameof(DataGridTemplateColumn.CellTemplate));
}
}
}
}
base.OnPropertyChanged(change);
}
}

15
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@ -283,7 +283,11 @@ namespace Avalonia.Controls
//TODO TabStop
private void DataGridRowGroupHeader_PointerPressed(PointerPressedEventArgs e)
{
if (OwningGrid != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (OwningGrid == null)
{
return;
}
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (OwningGrid.IsDoubleClickRecordsClickOnCall(this) && !e.Handled)
{
@ -300,6 +304,15 @@ namespace Avalonia.Controls
e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
{
OwningGrid.Focus();
}
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
}
}
private void EnsureChildClip(Visual child, double frozenLeftEdge)

17
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@ -179,12 +179,12 @@ namespace Avalonia.Controls.Primitives
//TODO TabStop
private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (OwningGrid == null)
{
return;
}
if (OwningGrid != null)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
@ -199,6 +199,19 @@ namespace Avalonia.Controls.Primitives
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
Debug.Assert(sender is DataGridRowHeader);
Debug.Assert(sender == this);
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, -1, Slot, false);
}
}
}
}

22
src/Avalonia.Controls/Primitives/Popup.cs

@ -145,7 +145,9 @@ namespace Avalonia.Controls.Primitives
{
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>((x, e) => x.ChildChanged(e));
IsOpenProperty.Changed.AddClassHandler<Popup>((x, e) => x.IsOpenChanged((AvaloniaPropertyChangedEventArgs<bool>)e));
IsOpenProperty.Changed.AddClassHandler<Popup>((x, e) => x.IsOpenChanged((AvaloniaPropertyChangedEventArgs<bool>)e));
VerticalOffsetProperty.Changed.AddClassHandler<Popup>((x, _) => x.HandlePositionChange());
HorizontalOffsetProperty.Changed.AddClassHandler<Popup>((x, _) => x.HandlePositionChange());
}
/// <summary>
@ -519,6 +521,24 @@ namespace Avalonia.Controls.Primitives
base.OnDetachedFromLogicalTree(e);
Close();
}
private void HandlePositionChange()
{
if (_openState != null)
{
var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType<IControl>();
if (placementTarget == null)
return;
_openState.PopupHost.ConfigurePosition(
placementTarget,
PlacementMode,
new Point(HorizontalOffset, VerticalOffset),
PlacementAnchor,
PlacementGravity,
PlacementConstraintAdjustment,
PlacementRect);
}
}
private static IDisposable SubscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler, Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe)
{

4
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -75,8 +75,6 @@
</Grid.ColumnDefinitions>
<ContentPresenter Name="PART_IconPresenter"
Content="{TemplateBinding Icon}"
Width="16"
Height="16"
Margin="{DynamicResource MenuIconPresenterMargin}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
@ -199,6 +197,8 @@
</Style>
<Style Selector="MenuItem /template/ ContentPresenter#PART_IconPresenter">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="MenuItem:icon /template/ ContentPresenter#PART_IconPresenter">

7
src/Avalonia.Visuals/Visual.cs

@ -489,11 +489,8 @@ namespace Avalonia
protected internal sealed override void LogBindingError(AvaloniaProperty property, Exception e)
{
// Don't log a binding error unless the control is attached to a logical or visual tree.
// In theory this should only need to check for logical tree attachment, but in practise
// due to ContentControlMixin only taking effect when the template has finished being
// applied, some controls are attached to the visual tree before the logical tree.
if (((ILogical)this).IsAttachedToLogicalTree || ((IVisual)this).IsAttachedToVisualTree)
// Don't log a binding error unless the control is attached to a logical tree.
if (((ILogical)this).IsAttachedToLogicalTree)
{
if (e is BindingChainException b &&
string.IsNullOrEmpty(b.ExpressionErrorPoint) &&

23
src/Markup/Avalonia.Markup.Xaml/Extensions.cs

@ -2,7 +2,9 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml
{
@ -32,5 +34,26 @@ namespace Avalonia.Markup.Xaml
string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}";
return tr?.Resolve(name);
}
public static object GetDefaultAnchor(this IServiceProvider provider)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
object anchor = provider.GetFirstParent<IControl>();
if (anchor is null)
{
// Try to find IDataContextProvider, this was added to allow us to find
// a datacontext for Application class when using NativeMenuItems.
anchor = provider.GetFirstParent<IDataContextProvider>();
}
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
provider.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
provider.GetLastParent<IStyle>();
}
}
}

18
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@ -1,7 +1,5 @@
using System;
using Avalonia.Data;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using Avalonia.Data.Core;
using Avalonia.Markup.Parsers;
@ -33,24 +31,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Priority = Priority,
StringFormat = StringFormat,
Source = Source,
DefaultAnchor = new WeakReference(GetDefaultAnchor(provider))
DefaultAnchor = new WeakReference(provider.GetDefaultAnchor())
};
}
private static object GetDefaultAnchor(IServiceProvider provider)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
object anchor = provider.GetFirstParent<IControl>();
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
provider.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
provider.GetLastParent<IStyle>();
}
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
{
if (Source != null)

23
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs

@ -37,33 +37,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Source = Source,
StringFormat = StringFormat,
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)),
DefaultAnchor = new WeakReference(descriptorContext.GetDefaultAnchor()),
TargetNullValue = TargetNullValue,
NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
};
}
private static object GetDefaultAnchor(IServiceProvider context)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
object anchor = context.GetFirstParent<IControl>();
if(anchor is null)
{
// Try to find IDataContextProvider, this was added to allow us to find
// a datacontext for Application class when using NativeMenuItems.
anchor = context.GetFirstParent<IDataContextProvider>();
}
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
context.GetLastParent<IStyle>();
}
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }

1
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -14,6 +14,7 @@
<Import Project="..\..\build\SharedVersion.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" />
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
<ItemGroup>

73
tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs

@ -0,0 +1,73 @@
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Markup.Xaml;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Base.UnitTests.Logging
{
public class LoggingTests
{
[Fact]
public void Control_Should_Not_Log_Binding_Errors_When_Detached_From_Visual_Tree()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Base.UnitTests.Logging;assembly=Avalonia.UnitTests'>
<Panel Name='panel'>
<Rectangle Name='rect' Fill='{Binding $parent[Window].Background}'/>
</Panel>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var calledTimes = 0;
using var logSink = TestLogSink.Start((l, a, s, m, d) =>
{
if (l >= Avalonia.Logging.LogEventLevel.Warning)
{
calledTimes++;
}
});
var panel = window.FindControl<Panel>("panel");
var rect = window.FindControl<Rectangle>("rect");
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
panel.Children.Remove(rect);
Assert.Equal(0, calledTimes);
}
}
[Fact]
public void Control_Should_Log_Binding_Errors_When_No_Ancestor_With_Such_Name()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Base.UnitTests.Logging;assembly=Avalonia.UnitTests'>
<Panel>
<Rectangle Fill='{Binding $parent[Grid].Background}'/>
</Panel>
</Window>";
var calledTimes = 0;
using var logSink = TestLogSink.Start((l, a, s, m, d) =>
{
if (l >= Avalonia.Logging.LogEventLevel.Warning && s is Rectangle)
{
calledTimes++;
}
});
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.Equal(1, calledTimes);
}
}
}
}
Loading…
Cancel
Save