Browse Source

Merge branch 'master' into prs/viewbox

Steven Kirk 7 years ago
parent
commit
dba58812b6
82 changed files with 2646 additions and 743 deletions
  1. 5 0
      .ncrunch/Avalonia.Native.v3.ncrunchproject
  2. 1 1
      native/Avalonia.Native/inc/avalonia-native.h
  3. 9 0
      native/Avalonia.Native/src/OSX/common.h
  4. 10 14
      native/Avalonia.Native/src/OSX/main.mm
  5. 2 0
      native/Avalonia.Native/src/OSX/window.h
  6. 81 44
      native/Avalonia.Native/src/OSX/window.mm
  7. 1 1
      samples/ControlCatalog.Desktop/Program.cs
  8. 1 3
      samples/ControlCatalog/MainView.xaml
  9. 1 4
      samples/ControlCatalog/Pages/DialogsPage.xaml
  10. 10 4
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  11. 1 1
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  12. 8 8
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  13. 124 0
      samples/ControlCatalog/Pages/TabControlPage.xaml
  14. 80 0
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  15. 63 48
      samples/ControlCatalog/SideBar.xaml
  16. 1 0
      scripts/ReplaceNugetCache.sh
  17. 8 4
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  18. 1 1
      src/Avalonia.Controls/ColumnDefinition.cs
  19. 57 0
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  20. 152 2
      src/Avalonia.Controls/Grid.cs
  21. 56 17
      src/Avalonia.Controls/GridSplitter.cs
  22. 0 2
      src/Avalonia.Controls/ItemsControl.cs
  23. 1 4
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  24. 19 2
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  25. 4 2
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  26. 18 1
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  27. 22 9
      src/Avalonia.Controls/SystemDialog.cs
  28. 92 72
      src/Avalonia.Controls/TabControl.cs
  29. 73 0
      src/Avalonia.Controls/TabItem.cs
  30. 4 2
      src/Avalonia.Controls/Templates/TemplateExtensions.cs
  31. 0 1
      src/Avalonia.Controls/TextBox.cs
  32. 14 9
      src/Avalonia.Controls/Utils/GridLayout.cs
  33. 651 0
      src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs
  34. 19 19
      src/Avalonia.Controls/Window.cs
  35. 2 3
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  36. 4 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  37. 2 1
      src/Avalonia.Input/InputExtensions.cs
  38. 6 0
      src/Avalonia.Layout/Properties/AssemblyInfo.cs
  39. 23 0
      src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs
  40. 0 108
      src/Avalonia.Native/DeferredRendererProxy.cs
  41. 1 3
      src/Avalonia.Native/GlPlatformFeature.cs
  42. 2 2
      src/Avalonia.Native/WindowImpl.cs
  43. 3 5
      src/Avalonia.Native/WindowImplBase.cs
  44. 44 0
      src/Avalonia.OpenGL/EglContext.cs
  45. 5 48
      src/Avalonia.OpenGL/EglDisplay.cs
  46. 2 2
      src/Avalonia.OpenGL/EglGlPlatformFeature.cs
  47. 23 9
      src/Avalonia.OpenGL/EglGlPlatformSurface.cs
  48. 28 0
      src/Avalonia.OpenGL/EglSurface.cs
  49. 2 2
      src/Avalonia.OpenGL/IGlContext.cs
  50. 0 10
      src/Avalonia.OpenGL/IGlSurface.cs
  51. 8 4
      src/Avalonia.Styling/StyledElement.cs
  52. 2 0
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  53. 2 0
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  54. 19 8
      src/Avalonia.Themes.Default/ButtonSpinner.xaml
  55. 1 0
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  56. 13 16
      src/Avalonia.Themes.Default/NumericUpDown.xaml
  57. 8 14
      src/Avalonia.Themes.Default/ScrollBar.xaml
  58. 55 49
      src/Avalonia.Themes.Default/TabControl.xaml
  59. 39 0
      src/Avalonia.Themes.Default/TabItem.xaml
  60. 4 1
      src/Avalonia.Themes.Default/TextBox.xaml
  61. 118 59
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  62. 9 0
      src/Avalonia.Visuals/Rendering/IDeferredRendererLock.cs
  63. 17 0
      src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs
  64. 4 0
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  65. 5 3
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  66. 10 3
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  67. 2 0
      src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
  68. 12 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
  69. 5 3
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  70. 1 1
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  71. 1 6
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  72. 15 3
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  73. 5 4
      src/Windows/Avalonia.Win32/Win32Platform.cs
  74. 24 10
      src/Windows/Avalonia.Win32/WindowImpl.cs
  75. 14 0
      tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs
  76. 284 0
      tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs
  77. 68 77
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  78. 5 2
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  79. 4 0
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  80. 99 1
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs
  81. 54 4
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs
  82. 3 1
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

+ 5 - 0
.ncrunch/Avalonia.Native.v3.ncrunchproject

@@ -0,0 +1,5 @@
+<ProjectConfiguration>
+  <Settings>
+    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+  </Settings>
+</ProjectConfiguration>

+ 1 - 1
native/Avalonia.Native/inc/avalonia-native.h

@@ -213,7 +213,7 @@ AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
 
 AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
 {
-    virtual HRESULT ShowDialog (IUnknown**ppv) = 0;
+    virtual HRESULT ShowDialog (IAvnWindow* parent) = 0;
     virtual HRESULT SetCanResize(bool value) = 0;
     virtual HRESULT SetHasDecorations(bool value) = 0;
     virtual HRESULT SetTitle (void* utf8Title) = 0;

+ 9 - 0
native/Avalonia.Native/src/OSX/common.h

@@ -31,4 +31,13 @@ extern NSSize ToNSSize (AvnSize s);
 #define NSDebugLog(...) (void)0
 #endif
 
+template<typename T> inline T* objc_cast(id from) {
+    if(from == nil)
+        return nil;
+    if ([from isKindOfClass:[T class]]) {
+        return static_cast<T*>(from);
+    }
+    return nil;
+}
+
 #endif

+ 10 - 14
native/Avalonia.Native/src/OSX/main.mm

@@ -32,27 +32,23 @@ public:
 - (void) do;
 @end
 @implementation ThreadingInitializer
-
-pthread_mutex_t mutex;
-pthread_cond_t cond;
-
+{
+    int _fds[2];
+}
 - (void) runOnce
 {
-    pthread_mutex_lock(&mutex);
-    pthread_cond_signal(&cond);
-    pthread_mutex_unlock(&mutex);
+    char buf[]={0};
+    write(_fds[1], buf, 1);
 }
 
 - (void) do
 {
-    pthread_mutex_init(&mutex, NULL);
-    pthread_cond_init(&cond, NULL);
+    pipe(_fds);
     [[[NSThread alloc] initWithTarget:self selector:@selector(runOnce) object:nil] start];
-    pthread_mutex_lock(&mutex);
-    pthread_cond_wait(&cond, &mutex);
-    pthread_mutex_unlock(&mutex);
-    pthread_cond_destroy(&cond);
-    pthread_mutex_destroy(&mutex);
+    char buf[1];
+    read(_fds[0], buf, 1);
+    close(_fds[0]);
+    close(_fds[1]);
 }
 
 

+ 2 - 0
native/Avalonia.Native/src/OSX/window.h

@@ -18,6 +18,8 @@ class WindowBaseImpl;
 -(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
 -(void) setCanBecomeKeyAndMain;
 -(void) pollModalSession: (NSModalSession _Nonnull) session;
+-(void) restoreParentWindow;
+-(bool) shouldTryToHandleEvents;
 @end
 
 struct INSWindowHolder

+ 81 - 44
native/Avalonia.Native/src/OSX/window.mm

@@ -109,6 +109,7 @@ public:
             if(Window != nullptr)
             {
                 [Window orderOut:Window];
+                [Window restoreParentWindow];
             }
             
             return S_OK;
@@ -392,30 +393,6 @@ protected:
     }
 };
 
-class ModalDisposable : public ComUnknownObject
-{
-    NSModalSession _session;
-    AvnWindow* _window;
-    
-    void Dispose ()
-    {
-        [_window orderOut:_window];
-        [NSApp endModalSession:_session];
-    }
-    
-public:
-    ModalDisposable(AvnWindow* window, NSModalSession session)
-    {
-        _session = session;
-        _window = window;
-    }
-    
-    virtual ~ModalDisposable()
-    {
-        Dispose();
-    }
-};
-
 class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
 {
 private:
@@ -444,32 +421,27 @@ private:
     {
         @autoreleasepool
         {
+            if([Window parentWindow] != nil)
+                [[Window parentWindow] removeChildWindow:Window];
             WindowBaseImpl::Show();
             
             return SetWindowState(_lastWindowState);
         }
     }
     
-    virtual HRESULT ShowDialog (IUnknown**ppv) override
+    virtual HRESULT ShowDialog (IAvnWindow* parent) override
     {
         @autoreleasepool
         {
-            if(ppv == nullptr)
-            {
+            if(parent == nullptr)
                 return E_POINTER;
-            }
-            
-            auto session = [NSApp beginModalSessionForWindow:Window];
-            auto disposable = new ModalDisposable(Window, session);
-            *ppv = disposable;
-            
-            SetPosition(lastPositionSet);
-            UpdateStyle();
-            
-            [Window setTitle:_lastTitle];
-            [Window setTitleVisibility:NSWindowTitleVisible];
+
+            auto cparent = dynamic_cast<WindowImpl*>(parent);
+            if(cparent == nullptr)
+                return E_INVALIDARG;
             
-            [Window pollModalSession:session];
+            [cparent->Window addChildWindow:Window ordered:NSWindowAbove];
+            WindowBaseImpl::Show();
             
             return S_OK;
         }
@@ -843,8 +815,19 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     [super viewDidChangeBackingProperties];
 }
 
+- (bool) ignoreUserInput
+{
+    auto parentWindow = objc_cast<AvnWindow>([self window]);
+    if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
+        return TRUE;
+    return FALSE;
+}
+
 - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
 {
+    if([self ignoreUserInput])
+        return;
+    
     [self becomeFirstResponder];
     auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
     auto avnPoint = [self toAvnPoint:localPoint];
@@ -952,7 +935,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
 } 
 
 - (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
-{ 
+{
+    if([self ignoreUserInput])
+        return;
     auto key = s_KeyMap[[event keyCode]];
     
     auto timestamp = [event timestamp] * 1000;
@@ -1125,13 +1110,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     {
         ComPtr<WindowBaseImpl> parent = _parent;
         _parent = NULL;
+        [self restoreParentWindow];
         parent->BaseEvents->Closed();
         [parent->View onClosed];
-        [self setContentView: nil];
+        dispatch_async(dispatch_get_main_queue(), ^{
+            [self setContentView: nil];
+        });
     }
 }
 
-
 -(BOOL)canBecomeKeyWindow
 {
     return _canBecomeKeyAndMain;
@@ -1142,12 +1129,62 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     return _canBecomeKeyAndMain;
 }
 
+-(bool) activateAppropriateChild: (bool)activating
+{
+    for(NSWindow* uch in [self childWindows])
+    {
+        auto ch = objc_cast<AvnWindow>(uch);
+        if(ch == nil)
+            continue;
+        [ch activateAppropriateChild:false];
+        return FALSE;
+    }
+    
+    if(!activating)
+        [self makeKeyAndOrderFront:self];
+    return TRUE;
+}
+
+-(bool)shouldTryToHandleEvents
+{
+    for(NSWindow* uch in [self childWindows])
+    {
+        auto ch = objc_cast<AvnWindow>(uch);
+        if(ch == nil)
+            continue;
+        return FALSE;
+    }
+    return TRUE;
+}
+
+-(void)makeKeyWindow
+{
+    if([self activateAppropriateChild: true])
+    {
+        [super makeKeyWindow];
+    }
+}
+
 -(void)becomeKeyWindow
 {
-    _parent->BaseEvents->Activated();
-    [super becomeKeyWindow];
+    if([self activateAppropriateChild: true])
+    {
+        _parent->BaseEvents->Activated();
+        [super becomeKeyWindow];
+    }
+}
+
+-(void) restoreParentWindow;
+{
+    auto parent = objc_cast<AvnWindow>([self parentWindow]);
+    if(parent != nil)
+    {
+        [parent removeChildWindow:self];
+        [parent activateAppropriateChild: false];
+    }
 }
 
+
 - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
 {
     return true;

+ 1 - 1
samples/ControlCatalog.Desktop/Program.cs

@@ -22,7 +22,7 @@ namespace ControlCatalog
         /// This method is needed for IDE previewer infrastructure
         /// </summary>
         public static AppBuilder BuildAvaloniaApp()
-            => AppBuilder.Configure<App>().LogToDebug().UsePlatformDetect();
+            => AppBuilder.Configure<App>().LogToDebug().UsePlatformDetect().UseReactiveUI();
 
         private static void ConfigureAssetAssembly(AppBuilder builder)
         {

+ 1 - 3
samples/ControlCatalog/MainView.xaml

@@ -2,9 +2,6 @@
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <TabControl Classes="sidebar" Name="Sidebar">
-    <TabControl.PageTransition>
-      <CrossFade Duration="0.25"/>
-    </TabControl.PageTransition>
     <TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></TabItem>
@@ -30,5 +27,6 @@
     <TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
     <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
     <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
+    <TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
   </TabControl>
 </UserControl>

+ 1 - 4
samples/ControlCatalog/Pages/DialogsPage.xaml

@@ -3,10 +3,7 @@
       <Button Name="OpenFile">Open File</Button>
       <Button Name="SaveFile">Save File</Button>
       <Button Name="SelectFolder">Select Folder</Button>
-      <StackPanel Orientation="Horizontal">
-          <CheckBox Name="IsModal" IsChecked="True"/>
-          <TextBlock>Modal to window</TextBlock>
-      </StackPanel>
       <Button Name="DecoratedWindow">Decorated window</Button>
+      <Button Name="Dialog">Dialog</Button>
   </StackPanel>
 </UserControl>

+ 10 - 4
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@@ -31,12 +31,18 @@ namespace ControlCatalog.Pages
                 }.ShowAsync(GetWindow());
             };
             this.FindControl<Button>("DecoratedWindow").Click += delegate
-            {
-                new DecoratedWindow().Show();
-            };
+                {
+                    new DecoratedWindow().ShowDialog(GetWindow());
+                };
+            this.FindControl<Button>("Dialog").Click += delegate
+                {
+                    new MainWindow().ShowDialog(GetWindow());
+                };
+
+
         }
 
-        Window GetWindow() => this.FindControl<CheckBox>("IsModal").IsChecked.Value ? (Window)this.VisualRoot : null;
+        Window GetWindow() => (Window)this.VisualRoot;
 
         private void InitializeComponent()
         {

+ 1 - 1
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@@ -83,7 +83,7 @@ namespace ControlCatalog.Pages
         public async Task Open()
         {
             var dialog = new OpenFileDialog();
-            var result = await dialog.ShowAsync();
+            var result = await dialog.ShowAsync(App.Current.MainWindow);
 
             if (result != null)
             {

+ 8 - 8
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@@ -6,7 +6,7 @@
 
     <TextBlock Margin="2,5,2,2" FontSize="14" FontWeight="Bold">Features:</TextBlock>
     <Grid Margin="2" ColumnDefinitions="Auto,Auto,Auto,Auto" RowDefinitions="Auto,Auto">
-      <Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto, Auto" RowDefinitions="35,35,35,35,35">
+      <Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto, Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
         <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">ShowButtonSpinner:</TextBlock>
         <CheckBox Grid.Row="0" Grid.Column="1" IsChecked="{Binding #upDown.ShowButtonSpinner}" VerticalAlignment="Center" Margin="2"/>
 
@@ -20,7 +20,7 @@
         <CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #upDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/>
 
       </Grid>
-      <Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="35,35,35,35,35">
+      <Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
         <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
         <DropDown Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
                   VerticalAlignment="Center" Margin="2">
@@ -49,22 +49,22 @@
         <TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">Text:</TextBlock>
         <TextBox Grid.Row="4" Grid.Column="1" Text="{Binding #upDown.Text}" VerticalAlignment="Center" Margin="2" />
       </Grid>
-      <Grid Grid.Row="0" Grid.Column="2" Margin="10,2,2,2" RowDefinitions="35,35,35,35,35" ColumnDefinitions="Auto, 120">
+      <Grid Grid.Row="0" Grid.Column="2" Margin="10,2,2,2" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, 120">
         <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock>
         <NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}"
-                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" Width="70" HorizontalAlignment="Center"/>
 
         <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock>
         <NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}"
-                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+                       CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" Width="70" HorizontalAlignment="Center"/>
 
         <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock>
         <NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center"
-                       Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+                       Margin="2" Width="70" HorizontalAlignment="Center"/>
 
         <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
         <NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
-                       Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
+                       Margin="2" Width="70" HorizontalAlignment="Center"/>
 
       </Grid>
     </Grid>
@@ -72,7 +72,7 @@
     <StackPanel Margin="2,10,2,2" Orientation="Horizontal" Spacing="10">
       <TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of NumericUpDown:</TextBlock>
       <NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
-                     CultureInfo="en-US" VerticalAlignment="Center" Height="25" Width="100"
+                     CultureInfo="en-US" VerticalAlignment="Center" Width="100"
                      Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
     </StackPanel>
 

+ 124 - 0
samples/ControlCatalog/Pages/TabControlPage.xaml

@@ -0,0 +1,124 @@
+<UserControl xmlns="https://github.com/avaloniaui">
+    <DockPanel>
+        <TextBlock 
+            DockPanel.Dock="Top" 
+            Classes="h1"
+            Text="TabControl"
+            Margin="4">           
+        </TextBlock>
+        <TextBlock 
+            DockPanel.Dock="Top" 
+            Classes="h2"
+            Text="A tab control that displays a tab strip along with the content of the selected tab"
+            Margin="4">          
+        </TextBlock>
+        <Grid 
+            ColumnDefinitions="*,*" 
+            RowDefinitions="*,100">
+            <DockPanel
+                Grid.Column="0"
+                Margin="4">
+                <TextBlock
+                    DockPanel.Dock="Top"
+                    Classes="h1"
+                    Text="From Inline TabItems">
+                </TextBlock>
+                <TabControl
+                    Margin="0 16"
+                    TabStripPlacement="{Binding TabPlacement}">
+                    <TabItem>
+                        <TabItem.Header>
+                            <TextBlock
+                               Text="Arch"
+                               VerticalAlignment="Center"
+                               HorizontalAlignment="Center"
+                               Margin="8">
+                            </TextBlock>
+                        </TabItem.Header>
+                        <StackPanel Orientation="Vertical" Spacing="8">
+                            <TextBlock>This is the first page in the TabControl.</TextBlock>
+                            <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg" Width="300"/>
+                        </StackPanel>
+                    </TabItem>
+                    <TabItem>
+                        <TabItem.Header>
+                            <TextBlock
+                               Text="Leaf"
+                               VerticalAlignment="Center"
+                               HorizontalAlignment="Center"
+                               Margin="8">
+                            </TextBlock>
+                        </TabItem.Header>
+                        <StackPanel Orientation="Vertical" Spacing="8">
+                            <TextBlock>This is the second page in the TabControl.</TextBlock>
+                            <Image Source="resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg" Width="300"/>
+                        </StackPanel>
+                    </TabItem>
+                    <TabItem IsEnabled="False">
+                        <TabItem.Header>
+                            <TextBlock
+                               Text="Disabled"
+                               VerticalAlignment="Center"
+                               HorizontalAlignment="Center"
+                               Margin="8">
+                            </TextBlock>
+                        </TabItem.Header>
+                        <TextBlock>You should not see this.</TextBlock>
+                    </TabItem>
+                </TabControl>
+            </DockPanel>
+            <DockPanel
+                Grid.Column="1"
+                Margin="4">
+                <TextBlock
+                    DockPanel.Dock="Top"
+                    Classes="h1"
+                    Text="From DataTemplate">
+                </TextBlock>
+                <TabControl
+                    Items="{Binding Tabs}"
+                    Margin="0 16"
+                    TabStripPlacement="{Binding TabPlacement}">
+                    <TabControl.ItemTemplate>
+                        <DataTemplate>
+                            <TextBlock
+                                Text="{Binding Header}"
+                                VerticalAlignment="Center"
+                                HorizontalAlignment="Center"
+                                Margin="8">
+                            </TextBlock>
+                        </DataTemplate>
+                    </TabControl.ItemTemplate>
+                    <TabControl.ContentTemplate>
+                        <DataTemplate>
+                            <StackPanel Orientation="Vertical" Spacing="8">
+                                <TextBlock Text="{Binding Text}"/>
+                                <Image Source="{Binding Image}" Width="300"/>
+                            </StackPanel>
+                        </DataTemplate>
+                    </TabControl.ContentTemplate>
+                    <TabControl.Styles>
+                        <Style Selector="TabItem">
+                            <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
+                        </Style>
+                    </TabControl.Styles>
+                </TabControl>
+            </DockPanel>
+            <StackPanel
+                Grid.Row="1"
+                Grid.ColumnSpan="2"
+                Orientation="Horizontal"
+                Spacing="8"
+                HorizontalAlignment="Center"
+                VerticalAlignment="Center">
+                <TextBlock VerticalAlignment="Center">Tab Placement:</TextBlock>
+                <DropDown SelectedIndex="{Binding TabPlacement, Mode=TwoWay}">
+                    <DropDownItem>Left</DropDownItem>
+                    <DropDownItem>Bottom</DropDownItem>
+                    <DropDownItem>Right</DropDownItem>
+                    <DropDownItem>Top</DropDownItem>
+                </DropDown>
+            </StackPanel>
+        </Grid>
+    </DockPanel>
+</UserControl>

+ 80 - 0
samples/ControlCatalog/Pages/TabControlPage.xaml.cs

@@ -0,0 +1,80 @@
+using System;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media.Imaging;
+using Avalonia.Platform;
+
+using ReactiveUI;
+
+namespace ControlCatalog.Pages
+{
+    using System.Collections.Generic;
+
+    public class TabControlPage : UserControl
+    {
+        public TabControlPage()
+        {
+            InitializeComponent();
+
+            DataContext = new PageViewModel
+            {
+                Tabs = new[]
+                {
+                    new TabItemViewModel
+                    {
+                        Header = "Arch",
+                        Text = "This is the first templated tab page.",
+                        Image = LoadBitmap("resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg?assembly=ControlCatalog"),
+                    },
+                    new TabItemViewModel
+                    {
+                        Header = "Leaf",
+                        Text = "This is the second templated tab page.",
+                        Image = LoadBitmap("resm:ControlCatalog.Assets.maple-leaf-888807_640.jpg?assembly=ControlCatalog"),
+                    },
+                    new TabItemViewModel
+                    {
+                        Header = "Disabled",
+                        Text = "You should not see this.",
+                        IsEnabled = false,
+                    },
+                },
+                TabPlacement = Dock.Top,
+            };
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+        private IBitmap LoadBitmap(string uri)
+        {
+            var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
+            return new Bitmap(assets.Open(new Uri(uri)));
+        }
+
+        private class PageViewModel : ReactiveObject
+        {
+            private Dock _tabPlacement;
+
+            public TabItemViewModel[] Tabs { get; set; }
+
+            public Dock TabPlacement
+            {
+                get { return _tabPlacement; }
+                set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); }
+            }
+        }
+
+        private class TabItemViewModel
+        {
+            public string Header { get; set; }
+            public string Text { get; set; }
+            public IBitmap Image { get; set; }
+            public bool IsEnabled { get; set; } = true;           
+        }
+    }
+}

+ 63 - 48
samples/ControlCatalog/SideBar.xaml

@@ -1,52 +1,67 @@
 <Styles xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
-  <Style Selector="TabControl.sidebar">
-    <Setter Property="Template">
-      <ControlTemplate>
-        <DockPanel>
-          <ScrollViewer MinWidth="190" Background="{DynamicResource ThemeAccentBrush}" DockPanel.Dock="Left">
-            <TabStrip Name="PART_TabStrip"
-                      MemberSelector="{x:Static TabControl.HeaderSelector}"
-                      Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
-              <TabStrip.ItemsPanel>
-                <ItemsPanelTemplate>
-                  <StackPanel Orientation="Vertical"/>
-                </ItemsPanelTemplate>
-              </TabStrip.ItemsPanel>
-            </TabStrip>
-          </ScrollViewer>
-          <Carousel Name="PART_Content"
-                    Margin="8 0 0 0"
-                    MemberSelector="{x:Static TabControl.ContentSelector}"
-                    Items="{TemplateBinding Items}"
-                    SelectedIndex="{TemplateBinding SelectedIndex}"
-                    PageTransition="{TemplateBinding PageTransition}"
-                    Grid.Row="1"/>
-        </DockPanel>
-      </ControlTemplate>
-    </Setter>
-  </Style>
+    <Style Selector="TabControl.sidebar">
+        <Setter Property="TabStripPlacement" Value="Left"/>
+        <Setter Property="Padding" Value="8 0 0 0"/>
+        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush}"/>
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Border 
+                    Margin="{TemplateBinding Margin}"
+                    BorderBrush="{TemplateBinding BorderBrush}"
+                    BorderThickness="{TemplateBinding BorderThickness}">
+                    <DockPanel>
+                        <ScrollViewer
+                            Name="PART_ScrollViewer"
+                            HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
+                            VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+                            Background="{TemplateBinding Background}">
+                            <ItemsPresenter
+                                Name="PART_ItemsPresenter"
+                                MinWidth="190"                            
+                                Items="{TemplateBinding Items}"
+                                ItemsPanel="{TemplateBinding ItemsPanel}"
+                                ItemTemplate="{TemplateBinding ItemTemplate}"
+                                MemberSelector="{TemplateBinding MemberSelector}">
+                            </ItemsPresenter>
+                        </ScrollViewer>
+                        <ContentPresenter
+                            Name="PART_Content"
+                            Margin="{TemplateBinding Padding}"                           
+                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                            Content="{TemplateBinding SelectedContent}"
+                            ContentTemplate="{TemplateBinding SelectedContentTemplate}">
+                        </ContentPresenter>
+                    </DockPanel>
+                </Border>
+            </ControlTemplate>
+        </Setter>
+    </Style>
 
-  <Style Selector="TabControl.sidebar TabStripItem">
-    <Setter Property="Foreground" Value="White"/>
-    <Setter Property="FontSize" Value="14"/>
-    <Setter Property="Margin" Value="0"/>
-    <Setter Property="Padding" Value="16"/>
-    <Setter Property="Opacity" Value="0.5"/>
-    <Setter Property="Transitions">
-      <Transitions>
-        <DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
-      </Transitions>
-    </Setter>
-  </Style>
-
-  <Style Selector="TabControl.sidebar TabStripItem:pointerover">
-    <Setter Property="Opacity" Value="1"/>
-  </Style>
-
-  <Style Selector="TabControl.sidebar TabStripItem:selected">
-    <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
-    <Setter Property="Opacity" Value="1"/>
-  </Style>
+    <Style Selector="TabControl.sidebar > TabItem">       
+        <Setter Property="BorderThickness" Value="0"/>
+        <Setter Property="Foreground" Value="White"/>
+        <Setter Property="FontSize" Value="14"/>
+        <Setter Property="Margin" Value="0"/>
+        <Setter Property="Padding" Value="16"/>
+        <Setter Property="Opacity" Value="0.5"/>
+        <Setter Property="Transitions">
+            <Transitions>
+                <DoubleTransition Property="Opacity" Duration="0:0:0.5"/>
+            </Transitions>
+        </Setter>
+    </Style>
+    <Style Selector="TabControl.sidebar > TabItem:pointerover">
+        <Setter Property="Opacity" Value="1"/>
+    </Style>
+    <Style Selector="TabControl.sidebar > TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+        <Setter Property="Background" Value="Transparent"/>
+    </Style>
+    <Style Selector="TabControl.sidebar > TabItem:selected">
+        <Setter Property="Opacity" Value="1"/>
+    </Style>
+    <Style Selector="TabControl.sidebar > TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
+        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
+    </Style>
 </Styles>

+ 1 - 0
scripts/ReplaceNugetCache.sh

@@ -4,5 +4,6 @@
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/
  cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/
+ cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.native/$1/lib/netstandard2.0/
  
  

+ 8 - 4
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@@ -88,18 +88,21 @@ namespace Avalonia.Data.Core
             _subscriber(value);
         }
 
-        protected void ValueChanged(object value)
+        protected void ValueChanged(object value) => ValueChanged(value, true);
+
+        private void ValueChanged(object value, bool notify)
         {
             var notification = value as BindingNotification;
 
             if (notification == null)
             {
                 LastValue = new WeakReference(value);
+
                 if (Next != null)
                 {
-                    Next.Target = new WeakReference(value);
+                    Next.Target = LastValue;
                 }
-                else
+                else if (notify)
                 {
                     _subscriber(value);
                 }
@@ -110,7 +113,7 @@ namespace Avalonia.Data.Core
 
                 if (Next != null)
                 {
-                    Next.Target = new WeakReference(notification.Value);
+                    Next.Target = LastValue;
                 }
 
                 if (Next == null || notification.Error != null)
@@ -136,6 +139,7 @@ namespace Avalonia.Data.Core
             }
             else
             {
+                ValueChanged(AvaloniaProperty.UnsetValue, notify:false);
                 _listening = false;
             }
         }

+ 1 - 1
src/Avalonia.Controls/ColumnDefinition.cs

@@ -88,4 +88,4 @@ namespace Avalonia.Controls
             set { SetValue(WidthProperty, value); }
         }
     }
-}
+}

+ 57 - 0
src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs

@@ -0,0 +1,57 @@
+// 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.
+
+using Avalonia.Controls.Primitives;
+
+namespace Avalonia.Controls.Generators
+{    
+    public class TabItemContainerGenerator : ItemContainerGenerator<TabItem>
+    {
+        public TabItemContainerGenerator(TabControl owner)
+            : base(owner, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty)
+        {
+            Owner = owner;
+        }
+
+        public new TabControl Owner { get; }
+
+        protected override IControl CreateContainer(object item)
+        {
+            var tabItem = (TabItem)base.CreateContainer(item);
+
+            tabItem.ParentTabControl = Owner;
+
+            if (tabItem.HeaderTemplate == null)
+            {
+                tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty];
+            }
+
+            if (tabItem.Header == null)
+            {
+                if (item is IHeadered headered)
+                {
+                    tabItem.Header = headered.Header;
+                }
+                else
+                {
+                    if (!(tabItem.DataContext is IControl))
+                    {
+                        tabItem.Header = tabItem.DataContext;
+                    }
+                }
+            }
+
+            if (!(tabItem.Content is IControl))
+            {
+                tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty];
+            }
+
+            if (tabItem.Content == null)
+            {
+                tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty];
+            }
+
+            return tabItem;
+        }
+    }
+}

+ 152 - 2
src/Avalonia.Controls/Grid.cs

@@ -3,10 +3,13 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
+using System.Reactive.Linq;
 using System.Runtime.CompilerServices;
 using Avalonia.Collections;
 using Avalonia.Controls.Utils;
+using Avalonia.VisualTree;
 using JetBrains.Annotations;
 
 namespace Avalonia.Controls
@@ -44,6 +47,24 @@ namespace Avalonia.Controls
         public static readonly AttachedProperty<int> RowSpanProperty =
             AvaloniaProperty.RegisterAttached<Grid, Control, int>("RowSpan", 1);
 
+        public static readonly AttachedProperty<bool> IsSharedSizeScopeProperty =
+            AvaloniaProperty.RegisterAttached<Grid, Control, bool>("IsSharedSizeScope", false);
+
+        protected override void OnMeasureInvalidated()
+        {
+            base.OnMeasureInvalidated();
+            _sharedSizeHost?.InvalidateMeasure(this);
+        }
+
+        private SharedSizeScopeHost _sharedSizeHost;
+
+        /// <summary>
+        /// Defines the SharedSizeScopeHost private property. 
+        /// The ampersands are used to make accessing the property via xaml inconvenient.
+        /// </summary>
+        internal static readonly AttachedProperty<SharedSizeScopeHost> s_sharedSizeScopeHostProperty =
+            AvaloniaProperty.RegisterAttached<Grid, Control, SharedSizeScopeHost>("&&SharedSizeScopeHost");
+
         private ColumnDefinitions _columnDefinitions;
 
         private RowDefinitions _rowDefinitions;
@@ -51,6 +72,13 @@ namespace Avalonia.Controls
         static Grid()
         {
             AffectsParentMeasure<Grid>(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty);
+            IsSharedSizeScopeProperty.Changed.AddClassHandler<Control>(IsSharedSizeScopeChanged);
+        }
+
+        public Grid()
+        {
+            this.AttachedToVisualTree += Grid_AttachedToVisualTree;
+            this.DetachedFromVisualTree += Grid_DetachedFromVisualTree;
         }
 
         /// <summary>
@@ -77,6 +105,7 @@ namespace Avalonia.Controls
 
                 _columnDefinitions = value;
                 _columnDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure());
+                _columnDefinitions.CollectionChanged += (_, __) => InvalidateMeasure();
             }
         }
 
@@ -104,6 +133,7 @@ namespace Avalonia.Controls
 
                 _rowDefinitions = value;
                 _rowDefinitions.TrackItemPropertyChanged(_ => InvalidateMeasure());
+                _rowDefinitions.CollectionChanged += (_, __) => InvalidateMeasure();
             }
         }
 
@@ -271,6 +301,11 @@ namespace Avalonia.Controls
             _rowLayoutCache = rowLayout;
             _columnLayoutCache = columnLayout;
 
+            if (_sharedSizeHost?.ParticipatesInScope(this) ?? false)
+            {
+                _sharedSizeHost.UpdateMeasureStatus(this, rowResult, columnResult);
+            }
+
             return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
 
             // Measure each child only once.
@@ -319,9 +354,21 @@ namespace Avalonia.Controls
             var (safeColumns, safeRows) = GetSafeColumnRows();
             var columnLayout = _columnLayoutCache;
             var rowLayout = _rowLayoutCache;
+
+            var rowCache = _rowMeasureCache;
+            var columnCache = _columnMeasureCache;
+
+            if (_sharedSizeHost?.ParticipatesInScope(this) ?? false)
+            {
+                (rowCache, columnCache) = _sharedSizeHost.HandleArrange(this, _rowMeasureCache, _columnMeasureCache);
+            
+                rowCache = rowLayout.Measure(finalSize.Height, rowCache.LeanLengthList);
+                columnCache = columnLayout.Measure(finalSize.Width, columnCache.LeanLengthList);
+            }
+
             // Calculate for arrange result.
-            var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache);
-            var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache);
+            var columnResult = columnLayout.Arrange(finalSize.Width, columnCache);
+            var rowResult = rowLayout.Arrange(finalSize.Height, rowCache);
             // Arrange the children.
             foreach (var child in Children.OfType<Control>())
             {
@@ -350,6 +397,73 @@ namespace Avalonia.Controls
             return finalSize;
         }
 
+        /// <summary>
+        /// Tests whether this grid belongs to a shared size scope.
+        /// </summary>
+        /// <returns>True if the grid is registered in a shared size scope.</returns>
+        internal bool HasSharedSizeScope()
+        {
+            return _sharedSizeHost != null;
+        }
+
+        /// <summary>
+        /// Called when the SharedSizeScope for a given grid has changed.
+        /// Unregisters the grid from it's current scope and finds a new one (if any) 
+        /// </summary>
+        /// <remarks>
+        /// This method, while not efficient, correctly handles nested scopes, with any order of scope changes.
+        /// </remarks>
+        internal void SharedScopeChanged()
+        {
+            _sharedSizeHost?.UnegisterGrid(this);
+
+            _sharedSizeHost = null;
+            var scope = this.GetVisualAncestors().OfType<Control>()
+                .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty));
+
+            if (scope != null)
+            {
+                _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty);
+                _sharedSizeHost.RegisterGrid(this);
+            }
+
+            InvalidateMeasure();
+        }
+
+        /// <summary>
+        /// Callback when a grid is attached to the visual tree. Finds the innermost SharedSizeScope and registers the grid 
+        /// in it.
+        /// </summary>
+        /// <param name="sender">The source of the event.</param>
+        /// <param name="e">The event arguments.</param>
+        private void Grid_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+        {
+            var scope =
+                new Control[] { this }.Concat(this.GetVisualAncestors().OfType<Control>())
+                    .FirstOrDefault(c => c.GetValue(IsSharedSizeScopeProperty));
+
+            if (_sharedSizeHost != null)
+                throw new AvaloniaInternalException("Shared size scope already present when attaching to visual tree!");
+
+            if (scope != null)
+            {
+                _sharedSizeHost = scope.GetValue(s_sharedSizeScopeHostProperty);
+                _sharedSizeHost.RegisterGrid(this);
+            }
+        }
+
+        /// <summary>
+        /// Callback when a grid is detached from the visual tree. Unregisters the grid from its SharedSizeScope if any.
+        /// </summary>
+        /// <param name="sender">The source of the event.</param>
+        /// <param name="e">The event arguments.</param>
+        private void Grid_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+        {
+            _sharedSizeHost?.UnegisterGrid(this);
+            _sharedSizeHost = null;
+        }
+
+
         /// <summary>
         /// Get the safe column/columnspan and safe row/rowspan.
         /// This method ensures that none of the children has a column/row outside the bounds of the definitions.
@@ -426,5 +540,41 @@ namespace Avalonia.Controls
 
             return value;
         }
+
+        /// <summary>
+        /// Called when the value of <see cref="Grid.IsSharedSizeScopeProperty"/> changes for a control.
+        /// </summary>
+        /// <param name="source">The control that triggered the change.</param>
+        /// <param name="arg2">Change arguments.</param>
+        private static void IsSharedSizeScopeChanged(Control source, AvaloniaPropertyChangedEventArgs arg2)
+        {
+            var shouldDispose = (arg2.OldValue is bool d) && d;
+            if (shouldDispose)
+            {
+                var host = source.GetValue(s_sharedSizeScopeHostProperty) as SharedSizeScopeHost;
+                if (host == null)
+                    throw new AvaloniaInternalException("SharedScopeHost wasn't set when IsSharedSizeScope was true!");
+                host.Dispose();
+                source.ClearValue(s_sharedSizeScopeHostProperty);
+            }
+
+            var shouldAssign = (arg2.NewValue is bool a) && a;
+            if (shouldAssign)
+            {
+                if (source.GetValue(s_sharedSizeScopeHostProperty) != null)
+                    throw new AvaloniaInternalException("SharedScopeHost was already set when IsSharedSizeScope is only now being set to true!");
+                source.SetValue(s_sharedSizeScopeHostProperty, new SharedSizeScopeHost());
+            }
+
+            // if the scope has changed, notify the descendant grids that they need to update.
+            if (source.GetVisualRoot() != null && shouldAssign || shouldDispose)
+            {
+                var participatingGrids = new[] { source }.Concat(source.GetVisualDescendants()).OfType<Grid>();
+
+                foreach (var grid in participatingGrids)
+                    grid.SharedScopeChanged();
+
+            }
+        }
     }
 }

+ 56 - 17
src/Avalonia.Controls/GridSplitter.cs

@@ -44,26 +44,52 @@ namespace Avalonia.Controls
 
         protected override void OnDragDelta(VectorEventArgs e)
         {
+            // WPF doesn't change anything when spliter is in the last row/column
+            // but resizes the splitter row/column when it's the first one.
+            // this is different, but more internally consistent.
+            if (_prevDefinition == null || _nextDefinition == null)
+                return;
+
             var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y;
             double max;
             double min;
             GetDeltaConstraints(out min, out max);
             delta = Math.Min(Math.Max(delta, min), max);
-            foreach (var definition in _definitions)
+
+            var prevIsStar = IsStar(_prevDefinition);
+            var nextIsStar = IsStar(_nextDefinition);
+
+            if (prevIsStar && nextIsStar)
             {
-                if (definition == _prevDefinition)
-                {
-                    SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta);
-                }
-                else if (definition == _nextDefinition)
-                {
-                    SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta);
-                }
-                else if (IsStar(definition))
+                foreach (var definition in _definitions)
                 {
-                    SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars.
+                    if (definition == _prevDefinition)
+                    {
+                        SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta);
+                    }
+                    else if (definition == _nextDefinition)
+                    {
+                        SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta);
+                    }
+                    else if (IsStar(definition))
+                    {
+                        SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars.
+                    }
                 }
             }
+            else if (prevIsStar)
+            {
+                SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta);
+            }
+            else if (nextIsStar)
+            {
+                SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta);
+            }
+            else
+            {
+                SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta);
+                SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta);
+            }
         }
 
         private double GetActualLength(DefinitionBase definition)
@@ -71,7 +97,7 @@ namespace Avalonia.Controls
             if (definition == null)
                 return 0;
             var columnDefinition = definition as ColumnDefinition;
-            return columnDefinition?.ActualWidth ?? ((RowDefinition) definition).ActualHeight;
+            return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
         }
 
         private double GetMinLength(DefinitionBase definition)
@@ -79,7 +105,7 @@ namespace Avalonia.Controls
             if (definition == null)
                 return 0;
             var columnDefinition = definition as ColumnDefinition;
-            return columnDefinition?.MinWidth ?? ((RowDefinition) definition).MinHeight;
+            return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight;
         }
 
         private double GetMaxLength(DefinitionBase definition)
@@ -87,13 +113,13 @@ namespace Avalonia.Controls
             if (definition == null)
                 return 0;
             var columnDefinition = definition as ColumnDefinition;
-            return columnDefinition?.MaxWidth ?? ((RowDefinition) definition).MaxHeight;
+            return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight;
         }
 
         private bool IsStar(DefinitionBase definition)
         {
             var columnDefinition = definition as ColumnDefinition;
-            return columnDefinition?.Width.IsStar ?? ((RowDefinition) definition).Height.IsStar;
+            return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar;
         }
 
         private void SetLengthInStars(DefinitionBase definition, double value)
@@ -105,7 +131,20 @@ namespace Avalonia.Controls
             }
             else
             {
-                ((RowDefinition) definition).Height = new GridLength(value, GridUnitType.Star);
+                ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star);
+            }
+        }
+
+        private void SetLength(DefinitionBase definition, double value)
+        {
+            var columnDefinition = definition as ColumnDefinition;
+            if (columnDefinition != null)
+            {
+                columnDefinition.Width = new GridLength(value);
+            }
+            else
+            {
+                ((RowDefinition)definition).Height = new GridLength(value);
             }
         }
 
@@ -160,7 +199,7 @@ namespace Avalonia.Controls
             }
             if (_grid.Children.OfType<Control>() // Decision based on other controls in the same column
                 .Where(c => Grid.GetColumn(c) == col)
-                .Any(c => c.GetType() != typeof (GridSplitter)))
+                .Any(c => c.GetType() != typeof(GridSplitter)))
             {
                 return Orientation.Horizontal;
             }

+ 0 - 2
src/Avalonia.Controls/ItemsControl.cs

@@ -249,8 +249,6 @@ namespace Avalonia.Controls
                         if (containerControl != null)
                         {
                             ((ISetLogicalParent)containerControl).SetParent(this);
-                            containerControl.SetValue(TemplatedParentProperty, null);
-
                             containerControl.UpdateChild();
 
                             if (containerControl.Child != null)

+ 1 - 4
src/Avalonia.Controls/Platform/IWindowImpl.cs

@@ -30,10 +30,7 @@ namespace Avalonia.Platform
         /// <summary>
         /// Shows the window as a dialog.
         /// </summary>
-        /// <returns>
-        /// An <see cref="IDisposable"/> that should be used to close the window.
-        /// </returns>
-        IDisposable ShowDialog();
+        void ShowDialog(IWindowImpl parent);
 
         /// <summary>
         /// Enables or disables system window decorations (title bar, buttons, etc)

+ 19 - 2
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@@ -1,6 +1,8 @@
 // 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.
 
+using Avalonia.Controls.Templates;
+
 namespace Avalonia.Controls.Primitives
 {
     /// <summary>
@@ -12,7 +14,13 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="Header"/> property.
         /// </summary>
         public static readonly StyledProperty<object> HeaderProperty =
-            AvaloniaProperty.Register<ContentControl, object>(nameof(Header));
+            AvaloniaProperty.Register<HeaderedContentControl, object>(nameof(Header));
+
+        /// <summary>
+        /// Defines the <see cref="HeaderTemplate"/> property.
+        /// </summary>
+        public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
+            AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(HeaderTemplate));          
 
         /// <summary>
         /// Gets or sets the header content.
@@ -21,6 +29,15 @@ namespace Avalonia.Controls.Primitives
         {
             get { return GetValue(HeaderProperty); }
             set { SetValue(HeaderProperty, value); }
+        }     
+
+        /// <summary>
+        /// Gets or sets the data template used to display the header content of the control.
+        /// </summary>
+        public IDataTemplate HeaderTemplate
+        {
+            get { return GetValue(HeaderTemplateProperty); }
+            set { SetValue(HeaderTemplateProperty, value); }
         }
     }
-}
+}

+ 4 - 2
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -84,8 +84,10 @@ namespace Avalonia.Controls.Primitives
 
             if (screen != null)
             {
-                var screenX = Position.X + Bounds.Width - screen.Bounds.X;
-                var screenY = Position.Y + Bounds.Height - screen.Bounds.Y;
+                var scaling = VisualRoot.RenderScaling;
+
+                var screenX = Position.X + (Bounds.Width * scaling) - screen.Bounds.X;
+                var screenY = Position.Y + (Bounds.Height * scaling) - screen.Bounds.Y;
 
                 if (screenX > screen.Bounds.Width)
                 {

+ 18 - 1
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@@ -260,7 +260,7 @@ namespace Avalonia.Controls.Primitives
                     var child = template.Build(this);
                     var nameScope = new NameScope();
                     NameScope.SetNameScope((Control)child, nameScope);
-                    child.SetValue(TemplatedParentProperty, this);
+                    ApplyTemplatedParent(child);
                     RegisterNames(child, nameScope);
                     ((ISetLogicalParent)child).SetParent(this);
                     VisualChildren.Add(child);
@@ -326,6 +326,23 @@ namespace Avalonia.Controls.Primitives
             InvalidateMeasure();
         }
 
+        /// <summary>
+        /// Sets the TemplatedParent property for the created template children.
+        /// </summary>
+        /// <param name="control">The control.</param>
+        private void ApplyTemplatedParent(IControl control)
+        {
+            control.SetValue(TemplatedParentProperty, this);
+
+            foreach (var child in control.LogicalChildren)
+            {
+                if (child is IControl c)
+                {
+                    ApplyTemplatedParent(c);
+                }
+            }
+        }
+
         /// <summary>
         /// Registers each control with its name scope.
         /// </summary>

+ 22 - 9
src/Avalonia.Controls/SystemDialog.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
@@ -18,28 +19,40 @@ namespace Avalonia.Controls
 
     public class SaveFileDialog : FileDialog
     {
-        public string DefaultExtension { get; set; }        
+        public string DefaultExtension { get; set; }
 
-        public async Task<string> ShowAsync(Window window)
-            =>
-                ((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, window?.PlatformImpl)) ??
-                 new string[0]).FirstOrDefault();
+        public async Task<string> ShowAsync(Window parent)
+        {
+            if(parent == null)
+                throw new ArgumentNullException(nameof(parent));
+            return ((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>()
+                 .ShowFileDialogAsync(this, parent?.PlatformImpl)) ??
+             new string[0]).FirstOrDefault();
+        }
     }
 
     public class OpenFileDialog : FileDialog
     {
         public bool AllowMultiple { get; set; }
 
-        public Task<string[]> ShowAsync(Window window = null)
-            => AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, window?.PlatformImpl);
+        public Task<string[]> ShowAsync(Window parent)
+        {
+            if(parent == null)
+                throw new ArgumentNullException(nameof(parent));
+            return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, parent?.PlatformImpl);
+        }
     }
 
     public class OpenFolderDialog : FileSystemDialog
     {
         public string DefaultDirectory { get; set; }
 
-        public Task<string> ShowAsync(Window window = null)
-               => AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, window?.PlatformImpl);
+        public Task<string> ShowAsync(Window parent)
+        {
+            if(parent == null)
+                throw new ArgumentNullException(nameof(parent));
+            return AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, parent?.PlatformImpl);
+        }
     }
 
     public abstract class SystemDialog

+ 92 - 72
src/Avalonia.Controls/TabControl.cs

@@ -1,10 +1,12 @@
 // 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.
 
-using Avalonia.Animation;
 using Avalonia.Controls.Generators;
+using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
+using Avalonia.Input;
+using Avalonia.Layout;
 
 namespace Avalonia.Controls
 {
@@ -14,28 +16,46 @@ namespace Avalonia.Controls
     public class TabControl : SelectingItemsControl
     {
         /// <summary>
-        /// Defines the <see cref="PageTransition"/> property.
+        /// Defines the <see cref="TabStripPlacement"/> property.
         /// </summary>
-        public static readonly StyledProperty<IPageTransition> PageTransitionProperty =
-            Avalonia.Controls.Carousel.PageTransitionProperty.AddOwner<TabControl>();
+        public static readonly StyledProperty<Dock> TabStripPlacementProperty =
+            AvaloniaProperty.Register<TabControl, Dock>(nameof(TabStripPlacement), defaultValue: Dock.Top);
 
         /// <summary>
-        /// Defines an <see cref="IMemberSelector"/> that selects the content of a <see cref="TabItem"/>.
+        /// Defines the <see cref="HorizontalContentAlignment"/> property.
         /// </summary>
-        public static readonly IMemberSelector ContentSelector =
-            new FuncMemberSelector<object, object>(SelectContent);
+        public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
+            ContentControl.HorizontalContentAlignmentProperty.AddOwner<TabControl>();
 
         /// <summary>
-        /// Defines an <see cref="IMemberSelector"/> that selects the header of a <see cref="TabItem"/>.
+        /// Defines the <see cref="VerticalContentAlignment"/> property.
         /// </summary>
-        public static readonly IMemberSelector HeaderSelector =
-            new FuncMemberSelector<object, object>(SelectHeader);
+        public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
+            ContentControl.VerticalContentAlignmentProperty.AddOwner<TabControl>();
 
         /// <summary>
-        /// Defines the <see cref="TabStripPlacement"/> property.
+        /// Defines the <see cref="ContentTemplate"/> property.
         /// </summary>
-        public static readonly StyledProperty<Dock> TabStripPlacementProperty =
-            AvaloniaProperty.Register<TabControl, Dock>(nameof(TabStripPlacement), defaultValue: Dock.Top);
+        public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
+            ContentControl.ContentTemplateProperty.AddOwner<TabControl>();
+
+        /// <summary>
+        /// The selected content property
+        /// </summary>
+        public static readonly StyledProperty<object> SelectedContentProperty =
+            AvaloniaProperty.Register<TabControl, object>(nameof(SelectedContent));
+
+        /// <summary>
+        /// The selected content template property
+        /// </summary>
+        public static readonly StyledProperty<IDataTemplate> SelectedContentTemplateProperty =
+            AvaloniaProperty.Register<TabControl, IDataTemplate>(nameof(SelectedContentTemplate));
+
+        /// <summary>
+        /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
+        /// </summary>
+        private static readonly FuncTemplate<IPanel> DefaultPanel =
+            new FuncTemplate<IPanel>(() => new WrapPanel());
 
         /// <summary>
         /// Initializes static members of the <see cref="TabControl"/> class.
@@ -43,107 +63,107 @@ namespace Avalonia.Controls
         static TabControl()
         {
             SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
-            FocusableProperty.OverrideDefaultValue<TabControl>(false);
+            ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
             AffectsMeasure<TabControl>(TabStripPlacementProperty);
         }
 
         /// <summary>
-        /// Gets the pages portion of the <see cref="TabControl"/>'s template.
+        /// Gets or sets the horizontal alignment of the content within the control.
         /// </summary>
-        public IControl Pages
+        public HorizontalAlignment HorizontalContentAlignment
         {
-            get;
-            private set;
+            get { return GetValue(HorizontalContentAlignmentProperty); }
+            set { SetValue(HorizontalContentAlignmentProperty, value); }
         }
 
         /// <summary>
-        /// Gets the tab strip portion of the <see cref="TabControl"/>'s template.
+        /// Gets or sets the vertical alignment of the content within the control.
         /// </summary>
-        public IControl TabStrip
+        public VerticalAlignment VerticalContentAlignment
         {
-            get;
-            private set;
+            get { return GetValue(VerticalContentAlignmentProperty); }
+            set { SetValue(VerticalContentAlignmentProperty, value); }
         }
 
         /// <summary>
-        /// Gets or sets the transition to use when switching tabs.
+        /// Gets or sets the tabstrip placement of the TabControl.
         /// </summary>
-        public IPageTransition PageTransition
+        public Dock TabStripPlacement
         {
-            get { return GetValue(PageTransitionProperty); }
-            set { SetValue(PageTransitionProperty, value); }
+            get { return GetValue(TabStripPlacementProperty); }
+            set { SetValue(TabStripPlacementProperty, value); }
         }
 
         /// <summary>
-        /// Gets or sets the tabstrip placement of the tabcontrol.
+        /// Gets or sets the default data template used to display the content of the selected tab.
         /// </summary>
-        public Dock TabStripPlacement
+        public IDataTemplate ContentTemplate
         {
-            get { return GetValue(TabStripPlacementProperty); }
-            set { SetValue(TabStripPlacementProperty, value); }
+            get { return GetValue(ContentTemplateProperty); }
+            set { SetValue(ContentTemplateProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the content of the selected tab.
+        /// </summary>
+        /// <value>
+        /// The content of the selected tab.
+        /// </value>
+        public object SelectedContent
+        {
+            get { return GetValue(SelectedContentProperty); }
+            internal set { SetValue(SelectedContentProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the content template for the selected tab.
+        /// </summary>
+        /// <value>
+        /// The content template of the selected tab.
+        /// </value>
+        public IDataTemplate SelectedContentTemplate
+        {
+            get { return GetValue(SelectedContentTemplateProperty); }
+            internal set { SetValue(SelectedContentTemplateProperty, value); }
+        }
+
+        internal ItemsPresenter ItemsPresenterPart { get; private set; }
+
+        internal ContentPresenter ContentPart { get; private set; }
+
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         {
-            // TabControl doesn't actually create items - instead its TabStrip and Carousel
-            // children create the items. However we want it to be a SelectingItemsControl
-            // so that it has the Items/SelectedItem etc properties. In this case, we can
-            // return a null ItemContainerGenerator to disable the creation of item containers.
-            return null;
+            return new TabItemContainerGenerator(this);
         }
 
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
             base.OnTemplateApplied(e);
 
-            TabStrip = e.NameScope.Find<IControl>("PART_TabStrip");
-            Pages = e.NameScope.Find<IControl>("PART_Content");
+            ItemsPresenterPart = e.NameScope.Get<ItemsPresenter>("PART_ItemsPresenter");
+
+            ContentPart = e.NameScope.Get<ContentPresenter>("PART_Content");
         }
 
-        /// <summary>
-        /// Selects the content of a tab item.
-        /// </summary>
-        /// <param name="o">The tab item.</param>
-        /// <returns>The content.</returns>
-        private static object SelectContent(object o)
+        /// <inheritdoc/>
+        protected override void OnGotFocus(GotFocusEventArgs e)
         {
-            var content = o as IContentControl;
+            base.OnGotFocus(e);
 
-            if (content != null)
+            if (e.NavigationMethod == NavigationMethod.Directional)
             {
-                return content.Content;
+                e.Handled = UpdateSelectionFromEventSource(e.Source);
             }
-            else
-            {
-                return o;
-            }       
         }
 
-        /// <summary>
-        /// Selects the header of a tab item.
-        /// </summary>
-        /// <param name="o">The tab item.</param>
-        /// <returns>The content.</returns>
-        private static object SelectHeader(object o)
+        /// <inheritdoc/>
+        protected override void OnPointerPressed(PointerPressedEventArgs e)
         {
-            var headered = o as IHeadered;
-            var control = o as IControl;
+            base.OnPointerPressed(e);
 
-            if (headered != null)
-            {
-                return headered.Header ?? string.Empty;
-            }
-            else if (control != null)
-            {
-                // Non-headered control items should result in TabStripItems with empty content.
-                // If a TabStrip is created with non IHeadered controls as its items, don't try to
-                // display the control in the TabStripItem: the content portion will also try to 
-                // display this control, resulting in dual-parentage breakage.
-                return string.Empty;
-            }
-            else
+            if (e.MouseButton == MouseButton.Left)
             {
-                return o;
+                e.Handled = UpdateSelectionFromEventSource(e.Source);
             }
         }
     }

+ 73 - 0
src/Avalonia.Controls/TabItem.cs

@@ -11,12 +11,20 @@ namespace Avalonia.Controls
     /// </summary>
     public class TabItem : HeaderedContentControl, ISelectable
     {
+        /// <summary>
+        /// Defines the <see cref="TabStripPlacement"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Dock> TabStripPlacementProperty =
+            TabControl.TabStripPlacementProperty.AddOwner<TabItem>();
+
         /// <summary>
         /// Defines the <see cref="IsSelected"/> property.
         /// </summary>
         public static readonly StyledProperty<bool> IsSelectedProperty =
             ListBoxItem.IsSelectedProperty.AddOwner<TabItem>();
 
+        private TabControl _parentTabControl;
+
         /// <summary>
         /// Initializes static members of the <see cref="TabItem"/> class.
         /// </summary>
@@ -24,6 +32,19 @@ namespace Avalonia.Controls
         {
             SelectableMixin.Attach<TabItem>(IsSelectedProperty);
             FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
+            IsSelectedProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateSelectedContent);
+            DataContextProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateHeader);
+        }
+
+        /// <summary>
+        /// Gets the tab strip placement.
+        /// </summary>
+        /// <value>
+        /// The tab strip placement.
+        /// </value>
+        public Dock TabStripPlacement
+        {
+            get { return GetValue(TabStripPlacementProperty); }
         }
 
         /// <summary>
@@ -34,5 +55,57 @@ namespace Avalonia.Controls
             get { return GetValue(IsSelectedProperty); }
             set { SetValue(IsSelectedProperty, value); }
         }
+
+        internal TabControl ParentTabControl
+        {
+            get => _parentTabControl;
+            set => _parentTabControl = value;
+        }
+
+        private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj)
+        {
+            if (Header == null)
+            {
+                if (obj.NewValue is IHeadered headered)
+                {
+                    if (Header != headered.Header)
+                    {
+                        Header = headered.Header;
+                    }
+                }
+                else
+                {
+                    if (!(obj.NewValue is IControl))
+                    {
+                        Header = obj.NewValue;
+                    }
+                }
+            }
+            else
+            {
+                if (Header == obj.OldValue)
+                {
+                    Header = obj.NewValue;
+                }
+            }          
+        }
+
+        private void UpdateSelectedContent(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (!IsSelected)
+            {
+                return;
+            }
+
+            if (ParentTabControl.SelectedContentTemplate != ContentTemplate)
+            {
+                ParentTabControl.SelectedContentTemplate = ContentTemplate;
+            }
+
+            if (ParentTabControl.SelectedContent != Content)
+            {
+                ParentTabControl.SelectedContent = Content;
+            }
+        }
     }
 }

+ 4 - 2
src/Avalonia.Controls/Templates/TemplateExtensions.cs

@@ -24,12 +24,14 @@ namespace Avalonia.Controls.Templates
         {
             foreach (IControl child in control.GetVisualChildren())
             {
-                if (child.TemplatedParent == templatedParent)
+                var childTemplatedParent = child.TemplatedParent;
+
+                if (childTemplatedParent == templatedParent)
                 {
                     yield return child;
                 }
 
-                if (child.TemplatedParent != null)
+                if (childTemplatedParent != null)
                 {
                     foreach (var descendant in GetTemplateChildren(child, templatedParent))
                     {

+ 0 - 1
src/Avalonia.Controls/TextBox.cs

@@ -262,7 +262,6 @@ namespace Avalonia.Controls
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
             _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
-            _presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
 
             if (IsFocused)
             {

+ 14 - 9
src/Avalonia.Controls/Utils/GridLayout.cs

@@ -143,14 +143,17 @@ namespace Avalonia.Controls.Utils
         /// <param name="containerLength">
         /// The container length. Usually, it is the constraint of the <see cref="Layoutable.MeasureOverride"/> method.
         /// </param>
+        /// <param name="conventions">
+        /// Overriding conventions that allows the algorithm to handle external inputa 
+        /// </param>
         /// <returns>
         /// The measured result that containing the desired size and all the column/row lengths.
         /// </returns>
         [NotNull, Pure]
-        internal MeasureResult Measure(double containerLength)
+        internal MeasureResult Measure(double containerLength, IReadOnlyList<LengthConvention> conventions = null)
         {
             // Prepare all the variables that this method needs to use.
-            var conventions = _conventions.Select(x => x.Clone()).ToList();
+            conventions = conventions ?? _conventions.Select(x => x.Clone()).ToList();
             var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
             var aggregatedLength = 0.0;
             double starUnitLength;
@@ -248,7 +251,7 @@ namespace Avalonia.Controls.Utils
             // | min | max |     |           | min |     |  min max  | max |
             // |#des#| fix |#des#| fix | fix | fix | fix |   #des#   |#des#|
 
-            var desiredStarMin = AggregateAdditionalConventionsForStars(conventions);
+            var (minLengths, desiredStarMin) = AggregateAdditionalConventionsForStars(conventions);
             aggregatedLength += desiredStarMin;
 
             // M6/7. Determine the desired length of the grid for current container length. Its value is stored in desiredLength.
@@ -282,7 +285,7 @@ namespace Avalonia.Controls.Utils
 
             // Returns the measuring result.
             return new MeasureResult(containerLength, desiredLength, greedyDesiredLength,
-                conventions, dynamicConvention);
+                conventions, dynamicConvention, minLengths);
         }
 
         /// <summary>
@@ -306,14 +309,14 @@ namespace Avalonia.Controls.Utils
             if (finalLength - measure.ContainerLength > LayoutTolerance)
             {
                 // If the final length is larger, we will rerun the whole measure.
-                measure = Measure(finalLength);
+                measure = Measure(finalLength, measure.LeanLengthList);
             }
             else if (finalLength - measure.ContainerLength < -LayoutTolerance)
             {
                 // If the final length is smaller, we measure the M6/6 procedure only.
                 var dynamicConvention = ExpandStars(measure.LeanLengthList, finalLength);
                 measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength,
-                    measure.LeanLengthList, dynamicConvention);
+                    measure.LeanLengthList, dynamicConvention, measure.MinLengths);
             }
 
             return new ArrangeResult(measure.LengthList);
@@ -370,7 +373,7 @@ namespace Avalonia.Controls.Utils
         /// <param name="conventions">All the conventions that have almost been fixed except the rest *.</param>
         /// <returns>The total desired length of all the * length.</returns>
         [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private double AggregateAdditionalConventionsForStars(
+        private (List<double>, double) AggregateAdditionalConventionsForStars(
             IReadOnlyList<LengthConvention> conventions)
         {
             // 1. Determine all one-span column's desired widths or row's desired heights.
@@ -403,7 +406,7 @@ namespace Avalonia.Controls.Utils
                 lengthList[group.Key] = Math.Max(lengthList[group.Key], length > 0 ? length : 0);
             }
 
-            return lengthList.Sum() - fixedLength;
+            return (lengthList, lengthList.Sum() - fixedLength);
         }
 
         /// <summary>
@@ -638,13 +641,14 @@ namespace Avalonia.Controls.Utils
             /// Initialize a new instance of <see cref="MeasureResult"/>.
             /// </summary>
             internal MeasureResult(double containerLength, double desiredLength, double greedyDesiredLength,
-                IReadOnlyList<LengthConvention> leanConventions, IReadOnlyList<double> expandedConventions)
+                IReadOnlyList<LengthConvention> leanConventions, IReadOnlyList<double> expandedConventions, IReadOnlyList<double> minLengths)
             {
                 ContainerLength = containerLength;
                 DesiredLength = desiredLength;
                 GreedyDesiredLength = greedyDesiredLength;
                 LeanLengthList = leanConventions;
                 LengthList = expandedConventions;
+                MinLengths = minLengths;
             }
 
             /// <summary>
@@ -674,6 +678,7 @@ namespace Avalonia.Controls.Utils
             /// Gets the length list for each column/row.
             /// </summary>
             public IReadOnlyList<double> LengthList { get; }
+            public IReadOnlyList<double> MinLengths { get; }
         }
 
         /// <summary>

+ 651 - 0
src/Avalonia.Controls/Utils/SharedSizeScopeHost.cs

@@ -0,0 +1,651 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Reactive.Disposables;
+using System.Reactive.Subjects;
+using Avalonia.Collections;
+using Avalonia.Controls.Utils;
+using Avalonia.Layout;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Shared size scope implementation.
+    /// Shares the size information between participating grids.
+    /// An instance of this class is attached to every <see cref="Control"/> that has its
+    /// IsSharedSizeScope property set to true.
+    /// </summary>
+    internal sealed class SharedSizeScopeHost : IDisposable
+    {
+        private enum MeasurementState
+        {
+            Invalidated,
+            Measuring,
+            Cached
+        }
+
+        /// <summary>
+        /// Class containing the measured rows/columns for a single grid.
+        /// Monitors changes to the row/column collections as well as the SharedSizeGroup changes
+        /// for the individual items in those collections.
+        /// Notifies the <see cref="SharedSizeScopeHost"/> of SharedSizeGroup changes.
+        /// </summary>
+        private sealed class MeasurementCache : IDisposable
+        {
+            readonly CompositeDisposable _subscriptions;
+            readonly Subject<(string, string, MeasurementResult)> _groupChanged = new Subject<(string, string, MeasurementResult)>();
+
+            public ISubject<(string oldName, string newName, MeasurementResult result)> GroupChanged => _groupChanged;
+
+            public MeasurementCache(Grid grid)
+            {
+                Grid = grid;
+                Results = grid.RowDefinitions.Cast<DefinitionBase>()
+                    .Concat(grid.ColumnDefinitions)
+                    .Select(d => new MeasurementResult(grid, d))
+                    .ToList();
+
+                grid.RowDefinitions.CollectionChanged += DefinitionsCollectionChanged;
+                grid.ColumnDefinitions.CollectionChanged += DefinitionsCollectionChanged;
+
+
+                _subscriptions = new CompositeDisposable(
+                    Disposable.Create(() => grid.RowDefinitions.CollectionChanged -= DefinitionsCollectionChanged),
+                    Disposable.Create(() => grid.ColumnDefinitions.CollectionChanged -= DefinitionsCollectionChanged),
+                    grid.RowDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged),
+                    grid.ColumnDefinitions.TrackItemPropertyChanged(DefinitionPropertyChanged));
+
+            }
+
+            // method to be hooked up once RowDefinitions/ColumnDefinitions collections can be replaced on a grid
+            private void DefinitionsChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+            {
+                // route to collection changed as a Reset.
+                DefinitionsCollectionChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+            }
+
+            private void DefinitionPropertyChanged(Tuple<object, PropertyChangedEventArgs> propertyChanged)
+            {
+                if (propertyChanged.Item2.PropertyName == nameof(DefinitionBase.SharedSizeGroup))
+                {
+                    var result = Results.Single(mr => ReferenceEquals(mr.Definition, propertyChanged.Item1));
+                    var oldName = result.SizeGroup?.Name;
+                    var newName = (propertyChanged.Item1 as DefinitionBase).SharedSizeGroup;
+                    _groupChanged.OnNext((oldName, newName, result));
+                }
+            }
+
+            private void DefinitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+            {
+                int offset = 0;
+                if (sender is ColumnDefinitions)
+                    offset = Grid.RowDefinitions.Count;
+
+                var newItems = e.NewItems?.OfType<DefinitionBase>().Select(db => new MeasurementResult(Grid, db)).ToList() ?? new List<MeasurementResult>();
+                var oldItems = e.OldStartingIndex >= 0 
+                                    ? Results.GetRange(e.OldStartingIndex + offset, e.OldItems.Count) 
+                                    : new List<MeasurementResult>();
+
+                void NotifyNewItems()
+                {
+                    foreach (var item in newItems)
+                    {
+                        if (string.IsNullOrEmpty(item.Definition.SharedSizeGroup))
+                            continue;
+
+                        _groupChanged.OnNext((null, item.Definition.SharedSizeGroup, item));
+                    }
+                }
+
+                void NotifyOldItems()
+                {
+                    foreach (var item in oldItems)
+                    {
+                        if (string.IsNullOrEmpty(item.Definition.SharedSizeGroup))
+                            continue;
+
+                        _groupChanged.OnNext((item.Definition.SharedSizeGroup, null, item));
+                    }
+                }
+
+                switch (e.Action)
+                {
+                    case NotifyCollectionChangedAction.Add:
+                        Results.InsertRange(e.NewStartingIndex + offset, newItems);
+                        NotifyNewItems();
+                        break;
+
+                    case NotifyCollectionChangedAction.Remove:
+                        Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count);
+                        NotifyOldItems();
+                        break;
+
+                    case NotifyCollectionChangedAction.Move:
+                        Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count);
+                        Results.InsertRange(e.NewStartingIndex + offset, oldItems);
+                        break;
+
+                    case NotifyCollectionChangedAction.Replace:
+                        Results.RemoveRange(e.OldStartingIndex + offset, oldItems.Count);
+                        Results.InsertRange(e.NewStartingIndex + offset, newItems);
+
+                        NotifyOldItems();
+                        NotifyNewItems();
+
+                        break;
+
+                    case NotifyCollectionChangedAction.Reset:
+                        oldItems = Results;
+                        newItems = Results = Grid.RowDefinitions.Cast<DefinitionBase>()
+                            .Concat(Grid.ColumnDefinitions)
+                            .Select(d => new MeasurementResult(Grid, d))
+                            .ToList();
+                        NotifyOldItems();
+                        NotifyNewItems();
+
+                        break;
+                }
+            }
+
+
+            /// <summary>
+            /// Updates the Results collection with Grid Measure results. 
+            /// </summary>
+            /// <param name="rowResult">Result of the GridLayout.Measure method for the RowDefinitions in the grid.</param>
+            /// <param name="columnResult">Result of the GridLayout.Measure method for the ColumnDefinitions in the grid.</param>
+            public void UpdateMeasureResult(GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
+            {
+                MeasurementState = MeasurementState.Cached;
+                for (int i = 0; i < Grid.RowDefinitions.Count; i++)
+                {
+                    Results[i].MeasuredResult = rowResult.LengthList[i];
+                    Results[i].MinLength = rowResult.MinLengths[i];
+                }
+
+                for (int i = 0; i < Grid.ColumnDefinitions.Count; i++)
+                {
+                    Results[i + Grid.RowDefinitions.Count].MeasuredResult = columnResult.LengthList[i];
+                    Results[i + Grid.RowDefinitions.Count].MinLength = columnResult.MinLengths[i];
+                }
+            }
+
+            /// <summary>
+            /// Clears the measurement cache, in preparation for the Measure pass.
+            /// </summary>
+            public void InvalidateMeasure()
+            {
+                var newItems = new List<MeasurementResult>();
+                var oldItems = new List<MeasurementResult>();
+
+                MeasurementState = MeasurementState.Invalidated;
+
+                Results.ForEach(r =>
+                {
+                    r.MeasuredResult = double.NaN;
+                    r.SizeGroup?.Reset();
+                });
+            }
+
+            /// <summary>
+            /// Clears the <see cref="IObservable{T}"/> subscriptions.
+            /// </summary>
+            public void Dispose()
+            {
+                _subscriptions.Dispose();
+                _groupChanged.OnCompleted();
+            }
+
+            /// <summary>
+            /// Gets the <see cref="Grid"/> for which this cache has been created.
+            /// </summary>
+            public Grid Grid { get; }
+            
+            /// <summary>
+            /// Gets the <see cref="MeasurementState"/> of this cache.
+            /// </summary>
+            public MeasurementState MeasurementState { get; private set; }
+
+            /// <summary>
+            /// Gets the list of <see cref="MeasurementResult"/> instances.
+            /// </summary>
+            /// <remarks>
+            /// The list is a 1-1 map of the concatenation of RowDefinitions and ColumnDefinitions
+            /// </remarks>
+            public List<MeasurementResult> Results { get; private set; }
+        }
+
+
+        /// <summary>
+        /// Class containing the Measure result for a single Row/Column in a grid.
+        /// </summary>
+        private class MeasurementResult
+        {
+            public MeasurementResult(Grid owningGrid, DefinitionBase definition)
+            {
+                OwningGrid = owningGrid;
+                Definition = definition;
+                MeasuredResult = double.NaN;
+            }
+
+            /// <summary>
+            /// Gets the <see cref="RowDefinition"/>/<see cref="ColumnDefinition"/> related to this <see cref="MeasurementResult"/>
+            /// </summary>
+            public DefinitionBase Definition { get; }
+            
+            /// <summary>
+            /// Gets or sets the actual result of the Measure operation for this column.
+            /// </summary>
+            public double MeasuredResult { get; set; }
+            
+            /// <summary>
+            /// Gets or sets the Minimum constraint for a Row/Column - relevant for star Rows/Columns in unconstrained grids.
+            /// </summary>
+            public double MinLength { get; set; }
+            
+            /// <summary>
+            /// Gets or sets the <see cref="Group"/> that this result belongs to.
+            /// </summary>
+            public Group SizeGroup { get; set; }
+            
+            /// <summary>
+            /// Gets the Grid that is the parent of the Row/Column
+            /// </summary>
+            public Grid OwningGrid { get; }
+
+            /// <summary>
+            /// Calculates the effective length that this Row/Column wishes to enforce in the SharedSizeGroup.
+            /// </summary>
+            /// <returns>A tuple of length and the priority in the shared size group.</returns>
+            public (double length, int priority) GetPriorityLength()
+            {
+                var length = (Definition as ColumnDefinition)?.Width ?? ((RowDefinition)Definition).Height;
+
+                if (length.IsAbsolute)
+                    return (MeasuredResult, 1);
+                if (length.IsAuto)
+                    return (MeasuredResult, 2);
+                if (MinLength > 0)
+                    return (MinLength, 3);
+                return (MeasuredResult, 4);
+            }
+        }
+
+        /// <summary>
+        /// Visitor class used to gather the final length for a given SharedSizeGroup.
+        /// </summary>
+        /// <remarks>
+        /// The values are applied according to priorities defined in <see cref="MeasurementResult.GetPriorityLength"/>.
+        /// </remarks>
+        private class LentgthGatherer
+        {
+            /// <summary>
+            /// Gets the final Length to be applied to every Row/Column in a SharedSizeGroup
+            /// </summary>
+            public double Length { get; private set; }
+            private int gatheredPriority = 6;
+
+            /// <summary>
+            /// Visits the <paramref name="result"/> applying the result of <see cref="MeasurementResult.GetPriorityLength"/> to its internal cache.
+            /// </summary>
+            /// <param name="result">The <see cref="MeasurementResult"/> instance to visit.</param>
+            public void Visit(MeasurementResult result)
+            {
+                var (length, priority) = result.GetPriorityLength();
+
+                if (gatheredPriority < priority)
+                    return;
+
+                gatheredPriority = priority;
+                if (gatheredPriority == priority)
+                {
+                    Length = Math.Max(length,Length);
+                }
+                else
+                {
+                    Length = length;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Representation of a SharedSizeGroup, containing Rows/Columns with the same SharedSizeGroup property value.
+        /// </summary>
+        private class Group
+        {
+            private double? cachedResult;
+            private List<MeasurementResult> _results = new List<MeasurementResult>(); 
+
+            /// <summary>
+            /// Gets the name of the SharedSizeGroup.
+            /// </summary>
+            public string Name { get; }
+
+            public Group(string name)
+            {
+                Name = name;
+            }
+
+            /// <summary>
+            /// Gets the collection of the <see cref="MeasurementResult"/> instances.
+            /// </summary>
+            public IReadOnlyList<MeasurementResult> Results => _results;
+
+            /// <summary>
+            /// Gets the final, calculated length for all Rows/Columns in the SharedSizeGroup.
+            /// </summary>
+            public double CalculatedLength => (cachedResult ?? (cachedResult = Gather())).Value;
+
+            /// <summary>
+            /// Clears the previously cached result in preparation for measurement.
+            /// </summary>
+            public void Reset()
+            {
+                cachedResult = null;
+            }
+
+            /// <summary>
+            /// Ads a measurement result to this group and sets it's <see cref="MeasurementResult.SizeGroup"/> property
+            /// to this instance.
+            /// </summary>
+            /// <param name="result">The <see cref="MeasurementResult"/> to include in this group.</param>
+            public void Add(MeasurementResult result)
+            {
+                if (_results.Contains(result))
+                    throw new AvaloniaInternalException(
+                        $"SharedSizeScopeHost: Invalid call to Group.Add - The SharedSizeGroup {Name} already contains the passed result");
+
+                result.SizeGroup = this;
+                _results.Add(result);
+            }
+
+            /// <summary>
+            /// Removes the measurement result from this group and clears its <see cref="MeasurementResult.SizeGroup"/> value.
+            /// </summary>
+            /// <param name="result">The <see cref="MeasurementResult"/> to clear.</param>
+            public void Remove(MeasurementResult result)
+            {
+                if (!_results.Contains(result))
+                    throw new AvaloniaInternalException(
+                        $"SharedSizeScopeHost: Invalid call to Group.Remove - The SharedSizeGroup {Name} does not contain the passed result");
+                result.SizeGroup = null;
+                _results.Remove(result);
+            }
+
+
+            private double Gather()
+            {
+                var visitor = new LentgthGatherer();
+
+                _results.ForEach(visitor.Visit);
+
+                return visitor.Length;
+            }
+        }
+
+        private readonly AvaloniaList<MeasurementCache> _measurementCaches = new AvaloniaList<MeasurementCache>();
+        private readonly Dictionary<string, Group> _groups = new Dictionary<string, Group>();
+        private bool _invalidating;
+
+        /// <summary>
+        /// Removes the SharedSizeScope and notifies all affected grids of the change.
+        /// </summary>
+        public void Dispose()
+        {
+            while (_measurementCaches.Any())
+                _measurementCaches[0].Grid.SharedScopeChanged();
+        }
+
+        /// <summary>
+        /// Registers the grid in this SharedSizeScope, to be called when the grid is added to the visual tree. 
+        /// </summary>
+        /// <param name="toAdd">The <see cref="Grid"/> to add to this scope.</param>
+        internal void RegisterGrid(Grid toAdd)
+        {
+            if (_measurementCaches.Any(mc => ReferenceEquals(mc.Grid, toAdd)))
+                throw new AvaloniaInternalException("SharedSizeScopeHost: tried to register a grid twice!");
+
+            var cache = new MeasurementCache(toAdd);
+            _measurementCaches.Add(cache);
+            AddGridToScopes(cache);
+        }
+
+        /// <summary>
+        /// Removes the registration for a grid in this SharedSizeScope.
+        /// </summary>
+        /// <param name="toRemove">The <see cref="Grid"/> to remove.</param>
+        internal void UnegisterGrid(Grid toRemove)
+        {
+            var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, toRemove));
+            if (cache == null)
+                throw new AvaloniaInternalException("SharedSizeScopeHost: tried to unregister a grid that wasn't registered before!");
+
+            _measurementCaches.Remove(cache);
+            RemoveGridFromScopes(cache);
+            cache.Dispose();
+        }
+
+        /// <summary>
+        /// Helper method to check if a grid needs to forward its Mesure results to, and requrest Arrange results from this scope.
+        /// </summary>
+        /// <param name="toCheck">The <see cref="Grid"/> that should be checked.</param>
+        /// <returns>True if the grid should forward its calls.</returns>
+        internal bool ParticipatesInScope(Grid toCheck)
+        {
+            return _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, toCheck))
+                                    ?.Results.Any(r => r.SizeGroup != null) ?? false;
+        }
+
+        /// <summary>
+        /// Notifies the SharedSizeScope that a grid had requested its measurement to be invalidated.
+        /// Forwards the same call to all affected grids in this scope.
+        /// </summary>
+        /// <param name="grid">The <see cref="Grid"/> that had it's Measure invalidated.</param>
+        internal void InvalidateMeasure(Grid grid)
+        {
+            // prevent stack overflow
+            if (_invalidating)
+                return;
+            _invalidating = true;
+
+            InvalidateMeasureImpl(grid);
+
+            _invalidating = false;
+        }
+
+        /// <summary>
+        /// Updates the measurement cache with the results of the <paramref name="grid"/> measurement pass.
+        /// </summary>
+        /// <param name="grid">The <see cref="Grid"/> that has been measured.</param>
+        /// <param name="rowResult">Measurement result for the grid's <see cref="RowDefinitions"/></param>
+        /// <param name="columnResult">Measurement result for the grid's <see cref="ColumnDefinitions"/></param>
+        internal void UpdateMeasureStatus(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
+        {
+            var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
+            if (cache == null)
+                throw new AvaloniaInternalException("SharedSizeScopeHost: Attempted to update measurement status for a grid that wasn't registered!");
+
+            cache.UpdateMeasureResult(rowResult, columnResult);
+        }
+
+        /// <summary>
+        /// Calculates the measurement result including the impact of any SharedSizeGroups that might affect this grid.
+        /// </summary>
+        /// <param name="grid">The <see cref="Grid"/> that is being Arranged</param>
+        /// <param name="rowResult">The <paramref name="grid"/>'s cached measurement result.</param>
+        /// <param name="columnResult">The <paramref name="grid"/>'s cached measurement result.</param>
+        /// <returns>Row and column measurement result updated with the SharedSizeScope constraints.</returns>
+        internal (GridLayout.MeasureResult, GridLayout.MeasureResult) HandleArrange(Grid grid, GridLayout.MeasureResult rowResult, GridLayout.MeasureResult columnResult)
+        {
+            return (
+                 Arrange(grid.RowDefinitions, rowResult),
+                 Arrange(grid.ColumnDefinitions, columnResult)
+                 );
+        }
+
+        /// <summary>
+        /// Invalidates the measure of all grids affected by the SharedSizeGroups contained within.
+        /// </summary>
+        /// <param name="grid">The <see cref="Grid"/> that is being invalidated.</param>
+        private void InvalidateMeasureImpl(Grid grid)
+        {
+            var cache = _measurementCaches.FirstOrDefault(mc => ReferenceEquals(mc.Grid, grid));
+
+            if (cache == null)
+                throw new AvaloniaInternalException(
+                    $"SharedSizeScopeHost: InvalidateMeasureImpl - called with a grid not present in the internal cache");
+
+            // already invalidated the cache, early out.
+            if (cache.MeasurementState == MeasurementState.Invalidated)
+                return;
+
+            // we won't calculate, so we should not invalidate.
+            if (!ParticipatesInScope(grid))
+                return;
+
+            cache.InvalidateMeasure();
+
+            // maybe there is a condition to only call arrange on some of the calls?
+            grid.InvalidateMeasure();
+
+            // find all the scopes within the invalidated grid
+            var scopeNames = cache.Results
+                                  .Where(mr => mr.SizeGroup != null)
+                                  .Select(mr => mr.SizeGroup.Name)
+                                  .Distinct();
+            // find all grids related to those scopes
+            var otherGrids = scopeNames.SelectMany(sn => _groups[sn].Results)
+                                       .Select(r => r.OwningGrid)
+                                       .Where(g => g.IsMeasureValid)
+                                       .Distinct();
+
+            // invalidate them as well
+            foreach (var otherGrid in otherGrids)
+            {
+                InvalidateMeasureImpl(otherGrid);
+            }
+        }
+
+        /// <summary>
+        /// <see cref="IObserver{T}"/> callback notifying the scope that a <see cref="MeasurementResult"/> has changed its
+        /// SharedSizeGroup
+        /// </summary>
+        /// <param name="change">Old and New name (either can be null) of the SharedSizeGroup, as well as the result.</param>
+        private void SharedGroupChanged((string oldName, string newName, MeasurementResult result) change)
+        {
+            RemoveFromGroup(change.oldName, change.result);
+            AddToGroup(change.newName, change.result);
+        }
+
+        /// <summary>
+        /// Handles the impact of SharedSizeGroups on the Arrange of <see cref="RowDefinitions"/>/<see cref="ColumnDefinitions"/>
+        /// </summary>
+        /// <param name="definitions">Rows/Columns that were measured</param>
+        /// <param name="measureResult">The initial measurement result.</param>
+        /// <returns>Modified measure result</returns>
+        private GridLayout.MeasureResult Arrange(IReadOnlyList<DefinitionBase> definitions, GridLayout.MeasureResult measureResult)
+        {
+            var conventions = measureResult.LeanLengthList.ToList();
+            var lengths = measureResult.LengthList.ToList();
+            var desiredLength = 0.0;
+            for (int i = 0; i < definitions.Count; i++)
+            {
+                var definition = definitions[i];
+
+                // for empty SharedSizeGroups pass on unmodified result.
+                if (string.IsNullOrEmpty(definition.SharedSizeGroup))
+                {
+                    desiredLength += measureResult.LengthList[i];
+                    continue;
+                }
+
+                var group = _groups[definition.SharedSizeGroup];
+                // Length calculated over all Definitions participating in a SharedSizeGroup.
+                var length = group.CalculatedLength;
+
+                conventions[i] = new GridLayout.LengthConvention(
+                    new GridLength(length),
+                    measureResult.LeanLengthList[i].MinLength,
+                    measureResult.LeanLengthList[i].MaxLength
+                );
+                lengths[i] = length;
+                desiredLength += length;
+            }
+
+            return new GridLayout.MeasureResult(
+                    measureResult.ContainerLength,
+                    desiredLength,
+                    measureResult.GreedyDesiredLength,//??
+                    conventions,
+                    lengths,
+                    measureResult.MinLengths);
+        }
+
+        /// <summary>
+        /// Adds all measurement results for a grid to their repsective scopes.
+        /// </summary>
+        /// <param name="cache">The <see cref="MeasurementCache"/> for a grid to be added.</param>
+        private void AddGridToScopes(MeasurementCache cache)
+        {
+            cache.GroupChanged.Subscribe(SharedGroupChanged);
+
+            foreach (var result in cache.Results)
+            {
+                var scopeName = result.Definition.SharedSizeGroup;
+                AddToGroup(scopeName, result);
+            }
+        }
+
+        /// <summary>
+        /// Handles adding the <see cref="MeasurementResult"/> to a SharedSizeGroup.
+        /// Does nothing for empty SharedSizeGroups.
+        /// </summary>
+        /// <param name="scopeName">The name (can be null or empty) of the group to add the <paramref name="result"/> to.</param>
+        /// <param name="result">The <see cref="MeasurementResult"/> to add to a scope.</param>
+        private void AddToGroup(string scopeName, MeasurementResult result)
+        {
+            if (string.IsNullOrEmpty(scopeName))
+                return;
+
+            if (!_groups.TryGetValue(scopeName, out var group))
+                _groups.Add(scopeName, group = new Group(scopeName));
+
+            group.Add(result);
+        }
+
+        /// <summary>
+        /// Removes all measurement results for a grid from their respective scopes.
+        /// </summary>
+        /// <param name="cache">The <see cref="MeasurementCache"/> for a grid to be removed.</param>
+        private void RemoveGridFromScopes(MeasurementCache cache)
+        {
+            foreach (var result in cache.Results)
+            {
+                var scopeName = result.Definition.SharedSizeGroup;
+                RemoveFromGroup(scopeName, result);
+            }
+        }
+
+        /// <summary>
+        /// Handles removing the <see cref="MeasurementResult"/> from a SharedSizeGroup.
+        /// Does nothing for empty SharedSizeGroups.
+        /// </summary>
+        /// <param name="scopeName">The name (can be null or empty) of the group to remove the <paramref name="result"/> from.</param>
+        /// <param name="result">The <see cref="MeasurementResult"/> to remove from a scope.</param>
+        private void RemoveFromGroup(string scopeName, MeasurementResult result)
+        {
+            if (string.IsNullOrEmpty(scopeName))
+                return;
+
+            if (!_groups.TryGetValue(scopeName, out var group))
+                throw new AvaloniaInternalException($"SharedSizeScopeHost: The scope {scopeName} wasn't found in the shared size scope");
+
+            group.Remove(result);
+            if (!group.Results.Any())
+                _groups.Remove(scopeName);
+        }
+    }
+}

+ 19 - 19
src/Avalonia.Controls/Window.cs

@@ -397,11 +397,23 @@ namespace Avalonia.Controls
         /// <returns>
         /// A task that can be used to track the lifetime of the dialog.
         /// </returns>
-        public Task ShowDialog()
+        public Task ShowDialog(Window parent)
         {
-            return ShowDialog<object>();
+            return ShowDialog<object>(parent);
         }
 
+
+        /// <summary>
+        /// Shows the window as a dialog.
+        /// </summary>
+        /// <typeparam name="TResult">
+        /// The type of the result produced by the dialog.
+        /// </typeparam>
+        /// <returns>.
+        /// A task that can be used to retrieve the result of the dialog when it closes.
+        /// </returns>
+        public Task<TResult> ShowDialog<TResult>(Window parent) => ShowDialog<TResult>(parent.PlatformImpl);
+        
         /// <summary>
         /// Shows the window as a dialog.
         /// </summary>
@@ -411,8 +423,10 @@ namespace Avalonia.Controls
         /// <returns>.
         /// A task that can be used to retrieve the result of the dialog when it closes.
         /// </returns>
-        public Task<TResult> ShowDialog<TResult>()
+        public Task<TResult> ShowDialog<TResult>(IWindowImpl parent)
         {
+            if(parent == null)
+                throw new ArgumentNullException(nameof(parent));
             if (IsVisible)
             {
                 throw new InvalidOperationException("The window is already being shown.");
@@ -427,24 +441,18 @@ namespace Avalonia.Controls
 
             using (BeginAutoSizing())
             {
-                var affectedWindows = Application.Current.Windows.Where(w => w.IsEnabled && w != this).ToList();
-                var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault();
-                SetIsEnabled(affectedWindows, false);
 
-                var modal = PlatformImpl?.ShowDialog();
+                PlatformImpl?.ShowDialog(parent);
                 var result = new TaskCompletionSource<TResult>();
 
                 Renderer?.Start();
-
                 Observable.FromEventPattern<EventHandler, EventArgs>(
                     x => this.Closed += x,
                     x => this.Closed -= x)
                     .Take(1)
                     .Subscribe(_ =>
                     {
-                        modal?.Dispose();
-                        SetIsEnabled(affectedWindows, true);
-                        activated?.Activate();
+                        parent.Activate();
                         result.SetResult((TResult)(_dialogResult ?? default(TResult)));
                     });
 
@@ -452,14 +460,6 @@ namespace Avalonia.Controls
             }
         }
 
-        void SetIsEnabled(IEnumerable<Window> windows, bool isEnabled)
-        {
-            foreach (var window in windows)
-            {
-                window.IsEnabled = isEnabled;
-            }
-        }
-
         void SetWindowStartupLocation()
         {
             if (WindowStartupLocation == WindowStartupLocation.CenterScreen)

+ 2 - 3
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@@ -86,9 +86,8 @@ namespace Avalonia.DesignerSupport.Remote
         {
         }
 
-        public IDisposable ShowDialog()
+        public void ShowDialog(IWindowImpl parent)
         {
-            return Disposable.Empty;
         }
 
         public void SetSystemDecorations(bool enabled)
@@ -111,4 +110,4 @@ namespace Avalonia.DesignerSupport.Remote
         {
         }
     }
-}
+}

+ 4 - 2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -87,7 +87,9 @@ namespace Avalonia.DesignerSupport.Remote
         {
         }
 
-        public IDisposable ShowDialog() => Disposable.Empty;
+        public void ShowDialog(IWindowImpl parent)
+        {
+        }
 
         public void SetSystemDecorations(bool enabled)
         {
@@ -157,4 +159,4 @@ namespace Avalonia.DesignerSupport.Remote
         public Screen[] AllScreens { get; } =
             {new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)};
     }
-}
+}

+ 2 - 1
src/Avalonia.Input/InputExtensions.cs

@@ -45,7 +45,8 @@ namespace Avalonia.Input
             return element != null &&
                    element.IsVisible &&
                    element.IsHitTestVisible &&
-                   element.IsEnabledCore;
+                   element.IsEnabledCore &&
+                   element.IsAttachedToVisualTree;
         }
     }
 }

+ 6 - 0
src/Avalonia.Layout/Properties/AssemblyInfo.cs

@@ -0,0 +1,6 @@
+// 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.
+
+using Avalonia.Metadata;
+
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")]

+ 23 - 0
src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.Native.Interop;
+using Avalonia.Rendering;
+
+namespace Avalonia.Native
+{
+    public class AvaloniaNativeDeferredRendererLock : IDeferredRendererLock
+    {
+        private readonly IAvnWindowBase _window;
+
+        public AvaloniaNativeDeferredRendererLock(IAvnWindowBase window)
+        {
+            _window = window;
+        }
+        public IDisposable TryLock()
+        {
+            if (_window.TryLock())
+                return Disposable.Create(() => _window.Unlock());
+            return null;
+        }
+    }
+}

+ 0 - 108
src/Avalonia.Native/DeferredRendererProxy.cs

@@ -1,108 +0,0 @@
-// 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.
-
-using System;
-using System.Collections.Generic;
-using Avalonia.Native.Interop;
-using Avalonia.Rendering;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Native
-{
-    public class DeferredRendererProxy : IRenderer, IRenderLoopTask, IRenderLoop
-    {
-        public DeferredRendererProxy(IRenderRoot root, IAvnWindowBase window)
-        {
-            if (window != null)
-            {
-                _useLock = true;
-                window.AddRef();
-_window = new IAvnWindowBase(window.NativePointer);
-            }
-            _renderer = new DeferredRenderer(root, this);
-            _rendererTask = (IRenderLoopTask)_renderer;
-        }
-
-        void IRenderLoop.Add(IRenderLoopTask i)
-        {
-            AvaloniaLocator.Current.GetService<IRenderLoop>().Add(this);
-        }
-
-        void IRenderLoop.Remove(IRenderLoopTask i)
-        {
-            AvaloniaLocator.Current.GetService<IRenderLoop>().Remove(this);
-        }
-
-        private DeferredRenderer _renderer;
-        private IRenderLoopTask _rendererTask;
-        private IAvnWindowBase _window;
-        private bool _useLock;
-
-        public bool DrawFps{
-            get => _renderer.DrawFps;
-            set => _renderer.DrawFps = value;
-        }
-        public bool DrawDirtyRects 
-        {
-            get => _renderer.DrawDirtyRects;
-            set => _renderer.DrawDirtyRects = value;
-        }
-
-        public bool NeedsUpdate => _rendererTask.NeedsUpdate;
-
-        public void AddDirty(IVisual visual) => _renderer.AddDirty(visual);
-
-        public void Dispose()
-        {
-            _renderer.Dispose();
-            _window?.Dispose();
-            _window = null;
-        }
-        public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
-        {
-            return _renderer.HitTest(p, root, filter);
-        }
-
-        public void Paint(Rect rect)
-        {
-            if (NeedsUpdate)
-            {
-                Update(TimeSpan.FromMilliseconds(Environment.TickCount));
-            }
-
-            Render();
-        }
-
-        public void Resized(Size size) => _renderer.Resized(size);
-
-        public void Start() => _renderer.Start();
-
-        public void Stop() => _renderer.Stop();
-
-        public void Update(TimeSpan time)
-        {
-            _rendererTask.Update(time);
-        }
-
-        public void Render()
-        {
-            if(_useLock)
-            {
-                _rendererTask.Render();
-                return;
-            }
-            if (_window == null)
-                return;
-            if (!_window.TryLock())
-                return;
-            try
-            {
-                _rendererTask.Render();
-            }
-            finally
-            {
-                _window.Unlock();
-            }
-        }
-    }
-}

+ 1 - 3
src/Avalonia.Native/GlPlatformFeature.cs

@@ -58,10 +58,8 @@ namespace Avalonia.Native
 
         public IGlDisplay Display { get; }
 
-        public void MakeCurrent(IGlSurface surface)
+        public void MakeCurrent()
         {
-            if (surface != null)
-                throw new ArgumentException(nameof(surface));
             Context.MakeCurrent();
         }
     }

+ 2 - 2
src/Avalonia.Native/WindowImpl.cs

@@ -47,9 +47,9 @@ namespace Avalonia.Native
 
         public IAvnWindow Native => _native;
 
-        public IDisposable ShowDialog()
+        public void ShowDialog(IWindowImpl window)
         {
-            return _native.ShowDialog();
+            _native.ShowDialog(((WindowImpl)window).Native);
         }
 
         public void CanResize(bool value)

+ 3 - 5
src/Avalonia.Native/WindowImplBase.cs

@@ -186,10 +186,6 @@ namespace Avalonia.Native
 
             void IAvnWindowBaseEvents.RunRenderPriorityJobs()
             {
-                if (_parent._deferredRendering
-                    && _parent._lastRenderedLogicalSize != _parent.ClientSize)
-                    // Hack to trigger Paint event on the renderer
-                    _parent.Paint?.Invoke(new Rect());
                 Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
             }
         }
@@ -245,7 +241,9 @@ namespace Avalonia.Native
         public IRenderer CreateRenderer(IRenderRoot root)
         {
             if (_deferredRendering)
-                return new DeferredRendererProxy(root, _gpu ? _native : null);
+                return new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>(),
+                    rendererLock:
+                    _gpu ? new AvaloniaNativeDeferredRendererLock(_native) : null);
             return new ImmediateRenderer(root);
         }
 

+ 44 - 0
src/Avalonia.OpenGL/EglContext.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Reactive.Disposables;
+using System.Threading;
+
+namespace Avalonia.OpenGL
+{
+    public class EglContext : IGlContext
+    {
+        private readonly EglDisplay _disp;
+        private readonly EglInterface _egl;
+        private readonly object _lock = new object();
+
+        public EglContext(EglDisplay display, EglInterface egl, IntPtr ctx, IntPtr offscreenSurface)
+        {
+            _disp = display;
+            _egl = egl;
+            Context = ctx;
+            OffscreenSurface = offscreenSurface;
+        }
+
+        public IntPtr Context { get; }
+        public IntPtr OffscreenSurface { get; }
+        public IGlDisplay Display => _disp;
+
+        public IDisposable Lock()
+        {
+            Monitor.Enter(_lock);
+            return Disposable.Create(() => Monitor.Exit(_lock));
+        }
+
+        public void MakeCurrent()
+        {
+            if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context))
+                throw new OpenGlException("eglMakeCurrent failed");
+        }
+        
+        public void MakeCurrent(EglSurface surface)
+        {
+            var surf = ((EglSurface)surface)?.DangerousGetHandle() ?? OffscreenSurface;
+            if (!_egl.MakeCurrent(_disp.Handle, surf, surf, Context))
+                throw new OpenGlException("eglMakeCurrent failed");
+        }
+    }
+}

+ 5 - 48
src/Avalonia.OpenGL/EglDisplay.cs

@@ -12,7 +12,8 @@ namespace Avalonia.OpenGL
         private readonly IntPtr _display;
         private readonly IntPtr _config;
         private readonly int[] _contextAttributes;
-        
+
+        public IntPtr Handle => _display;
         public EglDisplay(EglInterface egl)
         {
             _egl = egl;  
@@ -121,7 +122,7 @@ namespace Avalonia.OpenGL
             });
             if (surf == IntPtr.Zero)
                 throw new OpenGlException("eglCreatePbufferSurface failed");
-            var rv = new EglContext(this, ctx, surf);
+            var rv = new EglContext(this, _egl, ctx, surf);
             rv.MakeCurrent(null);
             return rv;
         }
@@ -132,12 +133,12 @@ namespace Avalonia.OpenGL
                 throw new OpenGlException("eglMakeCurrent failed");
         }
 
-        public IGlSurface CreateWindowSurface(IntPtr window)
+        public EglSurface CreateWindowSurface(IntPtr window)
         {
             var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE});
             if (s == IntPtr.Zero)
                 throw new OpenGlException("eglCreateWindowSurface failed");
-            return new EglSurface(this, s);
+            return new EglSurface(this, _egl, s);
         }
 
         public int SampleCount
@@ -157,49 +158,5 @@ namespace Avalonia.OpenGL
                 return rv;
             }
         }
-
-        class EglSurface : SafeHandle, IGlSurface
-        {
-            private readonly EglDisplay _display;
-
-            public EglSurface(EglDisplay display, IntPtr surface)  : base(surface, true)
-            {
-                _display = display;
-            }
-
-            protected override bool ReleaseHandle()
-            {
-                _display._egl.DestroySurface(_display._display, handle);
-                return true;
-            }
-
-            public override bool IsInvalid => handle == IntPtr.Zero;
-
-            public IGlDisplay Display => _display;
-            public void SwapBuffers() => _display._egl.SwapBuffers(_display._display, handle);
-        }
-        
-        class EglContext : IGlContext
-        {
-            private readonly EglDisplay _disp;
-
-            public EglContext(EglDisplay display, IntPtr ctx, IntPtr offscreenSurface)
-            {
-                _disp = display;
-                Context = ctx;
-                OffscreenSurface = offscreenSurface;
-            }
-
-            public IntPtr Context { get; }
-            public IntPtr OffscreenSurface { get; }
-            public IGlDisplay Display => _disp;
-
-            public void MakeCurrent(IGlSurface surface)
-            {
-                var surf = ((EglSurface)surface)?.DangerousGetHandle() ?? OffscreenSurface;
-                if (!_disp._egl.MakeCurrent(_disp._display, surf, surf, Context))
-                    throw new OpenGlException("eglMakeCurrent failed");
-            }
-        }
     }
 }

+ 2 - 2
src/Avalonia.OpenGL/EglGlPlatformFeature.cs

@@ -7,7 +7,7 @@ namespace Avalonia.OpenGL
     {
         public IGlDisplay Display { get; set; }
         public IGlContext ImmediateContext { get; set; }
-        public IGlContext DeferredContext { get; set; }
+        public EglContext DeferredContext { get; set; }
 
         public static void TryInitialize()
         {
@@ -26,7 +26,7 @@ namespace Avalonia.OpenGL
                 {
                     Display = disp,
                     ImmediateContext = ctx,
-                    DeferredContext = disp.CreateContext(ctx)
+                    DeferredContext = (EglContext)disp.CreateContext(ctx)
                 };
             }
             catch(Exception e)

+ 23 - 9
src/Avalonia.OpenGL/EglGlPlatformSurface.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Threading;
 
 namespace Avalonia.OpenGL
 {
@@ -12,10 +13,10 @@ namespace Avalonia.OpenGL
         }
 
         private readonly EglDisplay _display;
-        private readonly IGlContext _context;
+        private readonly EglContext _context;
         private readonly IEglWindowGlPlatformSurfaceInfo _info;
         
-        public EglGlPlatformSurface(EglDisplay display, IGlContext context, IEglWindowGlPlatformSurfaceInfo info)
+        public EglGlPlatformSurface(EglDisplay display, EglContext context, IEglWindowGlPlatformSurfaceInfo info)
         {
             _display = display;
             _context = context;
@@ -30,11 +31,11 @@ namespace Avalonia.OpenGL
 
         class RenderTarget : IGlPlatformSurfaceRenderTarget
         {
-            private readonly IGlContext _context;
-            private readonly IGlSurface _glSurface;
+            private readonly EglContext _context;
+            private readonly EglSurface _glSurface;
             private readonly IEglWindowGlPlatformSurfaceInfo _info;
 
-            public RenderTarget(IGlContext context, IGlSurface glSurface, IEglWindowGlPlatformSurfaceInfo info)
+            public RenderTarget(EglContext context, EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info)
             {
                 _context = context;
                 _glSurface = glSurface;
@@ -45,21 +46,33 @@ namespace Avalonia.OpenGL
 
             public IGlPlatformSurfaceRenderingSession BeginDraw()
             {
-                _context.MakeCurrent(_glSurface);
-                return new Session(_context, _glSurface, _info);
+                var l = _context.Lock();
+                try
+                {
+                    _context.MakeCurrent(_glSurface);
+                    return new Session(_context, _glSurface, _info, l);
+                }
+                catch
+                {
+                    l.Dispose();
+                    throw;
+                }
             }
             
             class Session : IGlPlatformSurfaceRenderingSession
             {
                 private readonly IGlContext _context;
-                private readonly IGlSurface _glSurface;
+                private readonly EglSurface _glSurface;
                 private readonly IEglWindowGlPlatformSurfaceInfo _info;
+                private IDisposable _lock;
 
-                public Session(IGlContext context, IGlSurface glSurface, IEglWindowGlPlatformSurfaceInfo info)
+                public Session(IGlContext context, EglSurface glSurface, IEglWindowGlPlatformSurfaceInfo info,
+                    IDisposable @lock)
                 {
                     _context = context;
                     _glSurface = glSurface;
                     _info = info;
+                    _lock = @lock;
                 }
 
                 public void Dispose()
@@ -67,6 +80,7 @@ namespace Avalonia.OpenGL
                     _context.Display.GlInterface.Flush();
                     _glSurface.SwapBuffers();
                     _context.Display.ClearContext();
+                    _lock.Dispose();
                 }
 
                 public IGlDisplay Display => _context.Display;

+ 28 - 0
src/Avalonia.OpenGL/EglSurface.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.OpenGL
+{
+    public class EglSurface : SafeHandle
+    {
+        private readonly EglDisplay _display;
+        private readonly EglInterface _egl;
+
+        public EglSurface(EglDisplay display, EglInterface egl, IntPtr surface)  : base(surface, true)
+        {
+            _display = display;
+            _egl = egl;
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            _egl.DestroySurface(_display.Handle, handle);
+            return true;
+        }
+
+        public override bool IsInvalid => handle == IntPtr.Zero;
+
+        public IGlDisplay Display => _display;
+        public void SwapBuffers() => _egl.SwapBuffers(_display.Handle, handle);
+    }
+}

+ 2 - 2
src/Avalonia.OpenGL/IGlContext.cs

@@ -3,6 +3,6 @@ namespace Avalonia.OpenGL
     public interface IGlContext
     {
         IGlDisplay Display { get; }
-        void MakeCurrent(IGlSurface surface);
+        void MakeCurrent();
     }
-}
+}

+ 0 - 10
src/Avalonia.OpenGL/IGlSurface.cs

@@ -1,10 +0,0 @@
-using System;
-
-namespace Avalonia.OpenGL
-{
-    public interface IGlSurface : IDisposable
-    {
-        IGlDisplay Display { get; }
-        void SwapBuffers();
-    }
-}

+ 8 - 4
src/Avalonia.Styling/StyledElement.cs

@@ -49,8 +49,11 @@ namespace Avalonia
         /// <summary>
         /// Defines the <see cref="TemplatedParent"/> property.
         /// </summary>
-        public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty =
-            AvaloniaProperty.Register<StyledElement, ITemplatedControl>(nameof(TemplatedParent), inherits: true);
+        public static readonly DirectProperty<StyledElement, ITemplatedControl> TemplatedParentProperty =
+            AvaloniaProperty.RegisterDirect<StyledElement, ITemplatedControl>(
+                nameof(TemplatedParent),
+                o => o.TemplatedParent,
+                (o ,v) => o.TemplatedParent = v);
 
         private int _initCount;
         private string _name;
@@ -62,6 +65,7 @@ namespace Avalonia
         private Styles _styles;
         private bool _styled;
         private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
+        private ITemplatedControl _templatedParent;
         private bool _dataContextUpdating;
 
         /// <summary>
@@ -269,8 +273,8 @@ namespace Avalonia
         /// </summary>
         public ITemplatedControl TemplatedParent
         {
-            get { return GetValue(TemplatedParentProperty); }
-            internal set { SetValue(TemplatedParentProperty, value); }
+            get => _templatedParent;
+            internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value);
         }
 
         /// <summary>

+ 2 - 0
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@@ -52,5 +52,7 @@
         <sys:Double x:Key="FontSizeSmall">10</sys:Double>
         <sys:Double x:Key="FontSizeNormal">12</sys:Double>
         <sys:Double x:Key="FontSizeLarge">16</sys:Double>
+
+        <sys:Double x:Key="ScrollBarThickness">10</sys:Double>
     </Style.Resources>
 </Style>

+ 2 - 0
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@@ -52,5 +52,7 @@
         <sys:Double x:Key="FontSizeSmall">10</sys:Double>
         <sys:Double x:Key="FontSizeNormal">12</sys:Double>
         <sys:Double x:Key="FontSizeLarge">16</sys:Double>
+
+        <sys:Double x:Key="ScrollBarThickness">10</sys:Double>
     </Style.Resources>
 </Style>

+ 19 - 8
src/Avalonia.Themes.Default/ButtonSpinner.xaml

@@ -1,17 +1,17 @@
 <Styles xmlns="https://github.com/avaloniaui">
   <Style Selector="ButtonSpinner">
-    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLowBrush}"/>
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
     <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
     <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
     <Setter Property="VerticalContentAlignment" Value="Center"/>
   </Style>
   <Style Selector="ButtonSpinner /template/ RepeatButton">
-    <Setter Property="RepeatButton.Background" Value="Transparent"/>
-    <Setter Property="RepeatButton.BorderBrush" Value="Transparent"/>
+    <Setter Property="Background" Value="Transparent"/>
+    <Setter Property="BorderBrush" Value="Transparent"/>
   </Style>
   <Style Selector="ButtonSpinner /template/ RepeatButton:pointerover">
-    <Setter Property="RepeatButton.Background" Value="{DynamicResource ThemeControlMidBrush}"/>
-    <Setter Property="RepeatButton.BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
+    <Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}"/>
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
   </Style>
   <Style Selector="ButtonSpinner /template/ RepeatButton#PART_IncreaseButton">
     <Setter Property="Content">
@@ -42,7 +42,8 @@
   <Style Selector="ButtonSpinner:right">
     <Setter Property="Template">
       <ControlTemplate>
-        <Border Background="{TemplateBinding Background}"
+        <Border Name="border"
+                Background="{TemplateBinding Background}"
                 BorderBrush="{TemplateBinding BorderBrush}"
                 BorderThickness="{TemplateBinding BorderThickness}"
                 Margin="{TemplateBinding Padding}"
@@ -67,7 +68,8 @@
   <Style Selector="ButtonSpinner:left">
     <Setter Property="Template">
       <ControlTemplate>
-        <Border Background="{TemplateBinding Background}"
+        <Border Name="border"
+                Background="{TemplateBinding Background}"
                 BorderBrush="{TemplateBinding BorderBrush}"
                 BorderThickness="{TemplateBinding BorderThickness}"
                 Margin="{TemplateBinding Padding}"
@@ -89,4 +91,13 @@
       </ControlTemplate>
     </Setter>
   </Style>
-</Styles>
+  <Style Selector="ButtonSpinner:pointerover /template/ Border#border">
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
+  </Style>
+  <Style Selector="ButtonSpinner:focus /template/ Border#border">
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
+  </Style>
+  <Style Selector="ButtonSpinner:error /template/ Border#border">
+    <Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}"/>
+  </Style>
+</Styles>

+ 1 - 0
src/Avalonia.Themes.Default/DefaultTheme.xaml

@@ -29,6 +29,7 @@
   <StyleInclude Source="resm:Avalonia.Themes.Default.TabStripItem.xaml?assembly=Avalonia.Themes.Default"/>
   <!-- TabControl needs to come after TabStrip as it redefines the inner TabStrip.ItemsPanel-->
   <StyleInclude Source="resm:Avalonia.Themes.Default.TabControl.xaml?assembly=Avalonia.Themes.Default"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Default.TabItem.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.TextBox.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ToggleButton.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.Expander.xaml?assembly=Avalonia.Themes.Default"/>

+ 13 - 16
src/Avalonia.Themes.Default/NumericUpDown.xaml

@@ -1,10 +1,10 @@
 <Styles xmlns="https://github.com/avaloniaui">
   <Style Selector="NumericUpDown">
-    <Setter Property="TemplatedControl.BorderBrush" Value="{DynamicResource ThemeBorderLowBrush}"/>
-    <Setter Property="TemplatedControl.BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
-    <Setter Property="TemplatedControl.Background" Value="{DynamicResource ThemeBackgroundBrush}" />
-    <Setter Property="TemplatedControl.Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
-    <Setter Property="TemplatedControl.Template">
+    <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
+    <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
+    <Setter Property="Padding" Value="4"/>
+    <Setter Property="Template">
       <ControlTemplate>
         <ButtonSpinner Name="PART_Spinner"
                        Background="{TemplateBinding Background}"
@@ -17,20 +17,13 @@
                        ButtonSpinnerLocation="{TemplateBinding ButtonSpinnerLocation}">
           <TextBox Name="PART_TextBox"
                    BorderThickness="0"
-                   Background="Transparent"
-                   ContextMenu="{TemplateBinding ContextMenu}"
-                   FontFamily="{TemplateBinding FontFamily}"
-                   FontSize="{TemplateBinding FontSize}"
-                   FontStyle="{TemplateBinding FontStyle}"
-                   FontWeight="{TemplateBinding FontWeight}"
-                   Foreground="{TemplateBinding Foreground}"
+                   Background="{TemplateBinding Background}" 
+                   BorderBrush="{TemplateBinding BorderBrush}"
+                   Padding="{TemplateBinding Padding}"
                    Watermark="{TemplateBinding Watermark}"
+                   DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
                    IsReadOnly="{TemplateBinding IsReadOnly}"
                    Text="{TemplateBinding Text}"
-                   Padding="{TemplateBinding Padding}"
-                   TextAlignment="Left"
-                   Margin="1"
-                   MinWidth="20"
                    AcceptsReturn="False"
                    TextWrapping="NoWrap">
           </TextBox>
@@ -38,4 +31,8 @@
       </ControlTemplate>
     </Setter>
   </Style>
+  <Style Selector="NumericUpDown /template/ TextBox#PART_TextBox">
+    <Setter Property="Margin" Value="4"/>
+    <Setter Property="MinWidth" Value="20"/>
+  </Style>
 </Styles>

+ 8 - 14
src/Avalonia.Themes.Default/ScrollBar.xaml

@@ -3,7 +3,7 @@
         <Setter Property="Template">
             <ControlTemplate>
                 <Border Background="{DynamicResource ThemeControlMidBrush}">
-                    <Grid RowDefinitions="10,*,10">
+                    <Grid RowDefinitions="Auto,*,Auto">
                         <RepeatButton Name="PART_LineUpButton"
                                       Classes="repeat"
                                       Grid.Row="0"
@@ -53,12 +53,11 @@
         </Setter>
     </Style>
     <Style Selector="ScrollBar:horizontal">
-        <Setter Property="Height"
-                Value="10" />
+        <Setter Property="Height" Value="{DynamicResource ScrollBarThickness}" />
         <Setter Property="Template">
             <ControlTemplate>
                 <Border Background="{DynamicResource ThemeControlMidBrush}">
-                    <Grid ColumnDefinitions="10,*,10">
+                    <Grid ColumnDefinitions="Auto,*,Auto">
                         <RepeatButton Name="PART_LineUpButton"
                                       Classes="repeat"
                                       Grid.Row="0"
@@ -108,22 +107,17 @@
         </Setter>
     </Style>
     <Style Selector="ScrollBar:horizontal /template/ Thumb#thumb">
-        <Setter Property="MinWidth"
-                Value="10" />
+        <Setter Property="MinWidth" Value="{DynamicResource ScrollBarThickness}" />
     </Style>
     <Style Selector="ScrollBar:vertical">
-        <Setter Property="Width"
-                Value="10" />
+        <Setter Property="Width" Value="{DynamicResource ScrollBarThickness}" />
     </Style>
     <Style Selector="ScrollBar:vertical /template/ Thumb#thumb">
-        <Setter Property="MinHeight"
-                Value="10" />
+        <Setter Property="MinHeight" Value="{DynamicResource ScrollBarThickness}" />
     </Style>
     <Style Selector="ScrollBar /template/ RepeatButton.repeat">
-        <Setter Property="Padding"
-                Value="2" />
-        <Setter Property="BorderThickness"
-                Value="0" />
+        <Setter Property="Padding" Value="2" />
+        <Setter Property="BorderThickness" Value="0" />
     </Style>
     <Style Selector="ScrollBar /template/ RepeatButton.repeattrack">
         <Setter Property="Template">

+ 55 - 49
src/Avalonia.Themes.Default/TabControl.xaml

@@ -1,50 +1,56 @@
 <Styles xmlns="https://github.com/avaloniaui"  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-  <Style Selector="TabControl">
-    <Setter Property="Template">
-      <ControlTemplate>
-        <Border Background="{TemplateBinding Background}"
-                BorderBrush="{TemplateBinding BorderBrush}"
-                BorderThickness="{TemplateBinding BorderThickness}">
-          <DockPanel>
-            <TabStrip Name="PART_TabStrip"
-                      MemberSelector="{x:Static TabControl.HeaderSelector}"
-                      Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"/>
-            <Carousel Name="PART_Content"
-                      MemberSelector="{x:Static TabControl.ContentSelector}"
-                      Items="{TemplateBinding Items}"
-                      SelectedIndex="{TemplateBinding SelectedIndex}"
-                      PageTransition="{TemplateBinding PageTransition}"
-                      Grid.Row="1"/>
-          </DockPanel>
-        </Border>
-      </ControlTemplate>
-    </Setter>
-  </Style>
-  <Style Selector="TabControl[TabStripPlacement=Top] /template/ TabStrip">
-    <Setter Property="DockPanel.Dock" Value="Top"/>
-  </Style>
-  <Style Selector="TabControl[TabStripPlacement=Bottom] /template/ TabStrip">
-    <Setter Property="DockPanel.Dock" Value="Bottom"/>
-  </Style>
-  <Style Selector="TabControl[TabStripPlacement=Left] /template/ TabStrip">
-    <Setter Property="DockPanel.Dock" Value="Left"/>
-    <Setter Property="ItemsPanel">
-      <Setter.Value>
-        <ItemsPanelTemplate>
-          <StackPanel Orientation="Vertical"/>
-        </ItemsPanelTemplate>
-      </Setter.Value>
-    </Setter>
-  </Style>
-  <Style Selector="TabControl[TabStripPlacement=Right] /template/ TabStrip">
-    <Setter Property="DockPanel.Dock" Value="Right"/>
-    <Setter Property="ItemsPanel">
-      <Setter.Value>
-        <ItemsPanelTemplate>
-          <StackPanel Orientation="Vertical"/>
-        </ItemsPanelTemplate>
-      </Setter.Value>
-    </Setter>
-  </Style>
-</Styles>
+    <Style Selector="TabControl">
+        <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
+        <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
+        <Setter Property="Padding" Value="4"/>
+        <Setter Property="VerticalContentAlignment" Value="Stretch"/>
+        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Border
+                    Margin="{TemplateBinding Margin}"
+                    BorderBrush="{TemplateBinding BorderBrush}"
+                    BorderThickness="{TemplateBinding BorderThickness}"
+                    Background="{TemplateBinding Background}"
+                    HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
+                    VerticalAlignment="{TemplateBinding VerticalAlignment}">
+                    <DockPanel>
+                        <ItemsPresenter
+                            Name="PART_ItemsPresenter"
+                            Items="{TemplateBinding Items}"
+                            ItemsPanel="{TemplateBinding ItemsPanel}"
+                            ItemTemplate="{TemplateBinding ItemTemplate}"
+                            MemberSelector="{TemplateBinding MemberSelector}"                     >
+                        </ItemsPresenter>
+                        <ContentPresenter
+                            Name="PART_Content"
+                            Margin="{TemplateBinding Padding}"
+                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                            Content="{TemplateBinding SelectedContent}"
+                            ContentTemplate="{TemplateBinding SelectedContentTemplate}">
+                        </ContentPresenter>
+                    </DockPanel>
+                </Border>
+            </ControlTemplate>
+        </Setter>
+    </Style>
+    <Style Selector="TabControl[TabStripPlacement=Top] /template/ ItemsPresenter#PART_ItemsPresenter">
+        <Setter Property="DockPanel.Dock" Value="Top"/>
+    </Style>
+    <Style Selector="TabControl[TabStripPlacement=Bottom] /template/ ItemsPresenter#PART_ItemsPresenter">
+        <Setter Property="DockPanel.Dock" Value="Bottom"/>
+    </Style>
+    <Style Selector="TabControl[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter">
+        <Setter Property="DockPanel.Dock" Value="Left"/>
+    </Style>
+    <Style Selector="TabControl[TabStripPlacement=Left] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
+        <Setter Property="Orientation" Value="Vertical"/>
+    </Style>
+    <Style Selector="TabControl[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter">
+        <Setter Property="DockPanel.Dock" Value="Right"/>
+    </Style>
+    <Style Selector="TabControl[TabStripPlacement=Right] /template/ ItemsPresenter#PART_ItemsPresenter > WrapPanel">
+        <Setter Property="Orientation" Value="Vertical"/>
+    </Style>
+</Styles>

+ 39 - 0
src/Avalonia.Themes.Default/TabItem.xaml

@@ -0,0 +1,39 @@
+<Styles xmlns="https://github.com/avaloniaui">
+    <Style Selector="TabItem">
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
+        <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLightBrush}"/>
+        <Setter Property="Template">
+            <ControlTemplate>
+                <ContentPresenter 
+                    Name="PART_ContentPresenter"
+                    Background="{TemplateBinding Background}"
+                    BorderBrush="{TemplateBinding BorderBrush}"
+                    BorderThickness="{TemplateBinding BorderThickness}"
+                    ContentTemplate="{TemplateBinding HeaderTemplate}"
+                    Content="{TemplateBinding Header}"
+                    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                    Padding="{TemplateBinding Padding}"/>
+            </ControlTemplate>
+        </Setter>
+    </Style>
+    <Style Selector="TabItem:disabled">
+        <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
+    </Style>
+    <Style Selector="TabItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+        <Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
+    </Style>
+    <Style Selector="TabItem:selected /template/ ContentPresenter#PART_ContentPresenter">
+        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
+    </Style>
+    <Style Selector="TabItem:selected:focus /template/ ContentPresenter#PART_ContentPresenter">
+        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
+    </Style>
+    <Style Selector="TabItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush3}"/>
+    </Style>
+    <Style Selector="TabItem:selected:focus:pointerover /template/ ContentPresenter#PART_ContentPresenter">
+        <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}"/>
+    </Style>
+</Styles>

+ 4 - 1
src/Avalonia.Themes.Default/TextBox.xaml

@@ -62,4 +62,7 @@
   <Style Selector="TextBox:error /template/ Border#border">
     <Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}"/>
   </Style>
-</Styles>
+  <Style Selector="TextBox">
+    <Setter Property="Cursor" Value="IBeam" />
+  </Style>
+</Styles>

+ 118 - 59
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -36,6 +36,8 @@ namespace Avalonia.Rendering
         private int _lastSceneId = -1;
         private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
         private IRef<IDrawOperation> _currentDraw;
+        private readonly IDeferredRendererLock _lock;
+        private readonly object _sceneLock = new object();
 
         /// <summary>
         /// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
@@ -44,11 +46,13 @@ namespace Avalonia.Rendering
         /// <param name="renderLoop">The render loop.</param>
         /// <param name="sceneBuilder">The scene builder to use. Optional.</param>
         /// <param name="dispatcher">The dispatcher to use. Optional.</param>
+        /// <param name="rendererLock">Lock object used before trying to access render target</param>
         public DeferredRenderer(
             IRenderRoot root,
             IRenderLoop renderLoop,
             ISceneBuilder sceneBuilder = null,
-            IDispatcher dispatcher = null)
+            IDispatcher dispatcher = null,
+            IDeferredRendererLock rendererLock = null)
         {
             Contract.Requires<ArgumentNullException>(root != null);
 
@@ -57,6 +61,7 @@ namespace Avalonia.Rendering
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
             Layers = new RenderLayers();
             _renderLoop = renderLoop;
+            _lock = rendererLock ?? new ManagedDeferredRendererLock();
         }
 
         /// <summary>
@@ -80,6 +85,7 @@ namespace Avalonia.Rendering
             RenderTarget = renderTarget;
             _sceneBuilder = sceneBuilder ?? new SceneBuilder();
             Layers = new RenderLayers();
+            _lock = new ManagedDeferredRendererLock();
         }
 
         /// <inheritdoc/>
@@ -114,8 +120,13 @@ namespace Avalonia.Rendering
         /// </summary>
         public void Dispose()
         {
-            var scene = Interlocked.Exchange(ref _scene, null);
-            scene?.Dispose();
+            lock (_sceneLock)
+            {
+                var scene = _scene;
+                _scene = null;
+                scene?.Dispose();
+            }
+
             Stop();
 
             Layers.Clear();
@@ -130,13 +141,18 @@ namespace Avalonia.Rendering
                 // When unit testing the renderLoop may be null, so update the scene manually.
                 UpdateScene();
             }
-
+            //It's safe to access _scene here without a lock since
+            //it's only changed from UI thread which we are currently on
             return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty<IVisual>();
         }
 
         /// <inheritdoc/>
         public void Paint(Rect rect)
         {
+            var t = (IRenderLoopTask)this;
+            if(t.NeedsUpdate)
+                UpdateScene();
+            Render(true);
         }
 
         /// <inheritdoc/>
@@ -164,17 +180,12 @@ namespace Avalonia.Rendering
             }
         }
 
-        bool IRenderLoopTask.NeedsUpdate => _dirty == null || _dirty.Count > 0;
+        bool NeedsUpdate => _dirty == null || _dirty.Count > 0;
+        bool IRenderLoopTask.NeedsUpdate => NeedsUpdate;
 
         void IRenderLoopTask.Update(TimeSpan time) => UpdateScene();
 
-        void IRenderLoopTask.Render()
-        {
-            using (var scene = _scene?.Clone())
-            {
-                Render(scene?.Item);
-            }
-        }
+        void IRenderLoopTask.Render() => Render(false);
 
         /// <inheritdoc/>
         Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
@@ -195,69 +206,105 @@ namespace Avalonia.Rendering
 
         internal void UnitTestUpdateScene() => UpdateScene();
 
-        internal void UnitTestRender() => Render(_scene.Item);
+        internal void UnitTestRender() => Render(false);
 
-        private void Render(Scene scene)
+        private void Render(bool forceComposite)
         {
-            bool renderOverlay = DrawDirtyRects || DrawFps;
-            bool composite = false;
-
-            if (RenderTarget == null)
-            {
-                RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
-            }
-
-            if (renderOverlay)
+            using (var l = _lock.TryLock())
             {
-                _dirtyRectsDisplay.Tick();
-            }
+                if (l == null)
+                    return;
 
-            try
-            {
-                if (scene != null && scene.Size != Size.Empty)
+                IDrawingContextImpl context = null;
+                try
                 {
-                    IDrawingContextImpl context = null;
-
-                    if (scene.Generation != _lastSceneId)
+                    try
                     {
-                        context = RenderTarget.CreateDrawingContext(this);
-                        Layers.Update(scene, context);
+                        IDrawingContextImpl GetContext()
+                        {
+                            if (context != null)
+                                return context;
+                            if (RenderTarget == null)
+                                RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
+                            return context = RenderTarget.CreateDrawingContext(this);
 
-                        RenderToLayers(scene);
+                        }
 
-                        if (DebugFramesPath != null)
+                        var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
+                        using (scene)
                         {
-                            SaveDebugFrames(scene.Generation);
+                            var overlay = DrawDirtyRects || DrawFps;
+                            if (DrawDirtyRects)
+                                _dirtyRectsDisplay.Tick();
+                            if (overlay)
+                                RenderOverlay(scene.Item, GetContext());
+                            if (updated || forceComposite || overlay)
+                                RenderComposite(scene.Item, GetContext());
                         }
+                    }
+                    finally
+                    {
+                        context?.Dispose();
+                    }
+                }
+                catch (RenderTargetCorruptedException ex)
+                {
+                    Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
+                    RenderTarget?.Dispose();
+                    RenderTarget = null;
+                }
+            }
+        }
 
-                        _lastSceneId = scene.Generation;
+        private (IRef<Scene> scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func<IDrawingContextImpl> contextFactory,
+            bool recursiveCall = false)
+        {
+            IRef<Scene> sceneRef;
+            lock (_sceneLock)
+                sceneRef = _scene?.Clone();
+            if (sceneRef == null)
+                return (null, false);
+            using (sceneRef)
+            {
+                var scene = sceneRef.Item;
+                if (scene.Generation != _lastSceneId)
+                {
+                    var context = contextFactory();
+                    Layers.Update(scene, context);
 
-                        composite = true;
-                    }
+                    RenderToLayers(scene);
 
-                    if (renderOverlay)
+                    if (DebugFramesPath != null)
                     {
-                        context = context ?? RenderTarget.CreateDrawingContext(this);
-                        RenderOverlay(scene, context);
-                        RenderComposite(scene, context);
+                        SaveDebugFrames(scene.Generation);
                     }
-                    else if (composite)
+
+                    lock (_sceneLock)
+                        _lastSceneId = scene.Generation;
+
+
+                    // We have consumed the previously available scene, but there might be some dirty 
+                    // rects since the last update. *If* we are on UI thread, we can force immediate scene
+                    // rebuild before rendering anything on-screen
+                    // We are calling the same method recursively here 
+                    if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate)
                     {
-                        context = context ?? RenderTarget.CreateDrawingContext(this);
-                        RenderComposite(scene, context);
+                        UpdateScene();
+                        var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true);
+                        return (rs, true);
                     }
-
-                    context?.Dispose();
+                    
+                    // Indicate that we have updated the layers
+                    return (sceneRef.Clone(), true);
                 }
+                
+                // Just return scene, layers weren't updated
+                return (sceneRef.Clone(), false);
             }
-            catch (RenderTargetCorruptedException ex)
-            {
-                Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
-                RenderTarget?.Dispose();
-                RenderTarget = null;
-            }
+            
         }
 
+
         private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
         {
             if (layer == null || node.LayerRoot == layer)
@@ -393,6 +440,11 @@ namespace Avalonia.Rendering
         private void UpdateScene()
         {
             Dispatcher.UIThread.VerifyAccess();
+            lock (_sceneLock)
+            {
+                if (_scene?.Item.Generation > _lastSceneId)
+                    return;
+            }
             if (_root.IsVisible)
             {
                 var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root));
@@ -411,16 +463,23 @@ namespace Avalonia.Rendering
                     }
                 }
 
-                var oldScene = Interlocked.Exchange(ref _scene, sceneRef);
-                oldScene?.Dispose();
+                lock (_sceneLock)
+                {
+                    var oldScene = _scene;
+                    _scene = sceneRef;
+                    oldScene?.Dispose();
+                }
 
                 _dirty.Clear();
-                (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size));
             }
             else
             {
-                var oldScene = Interlocked.Exchange(ref _scene, null);
-                oldScene?.Dispose();
+                lock (_sceneLock)
+                {
+                    var oldScene = _scene;
+                    _scene = null;
+                    oldScene?.Dispose();
+                }
             }
         }
 

+ 9 - 0
src/Avalonia.Visuals/Rendering/IDeferredRendererLock.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace Avalonia.Rendering
+{
+    public interface IDeferredRendererLock
+    {
+        IDisposable TryLock();
+    }
+}

+ 17 - 0
src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Reactive.Disposables;
+using System.Threading;
+
+namespace Avalonia.Rendering
+{
+    public class ManagedDeferredRendererLock : IDeferredRendererLock
+    {
+        private readonly object _lock = new object();
+        public IDisposable TryLock()
+        {
+            if (Monitor.TryEnter(_lock))
+                return Disposable.Create(() => Monitor.Exit(_lock));
+            return null;
+        }
+    }
+}

+ 4 - 0
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -67,6 +67,9 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_window_set_modal(GtkWindow window, bool modal);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_window_set_transient_for(GtkWindow window, IntPtr parent);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import
             public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow);
@@ -408,6 +411,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gtk_window_new GtkWindowNew;
         public static D.gtk_window_set_icon GtkWindowSetIcon;
         public static D.gtk_window_set_modal GtkWindowSetModal;
+        public static D.gtk_window_set_transient_for GtkWindowSetTransientFor;
         public static D.gdk_set_allowed_backends GdkSetAllowedBackends;
         public static D.gtk_init GtkInit;
         public static D.gtk_window_present GtkWindowPresent;

+ 5 - 3
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -222,14 +222,14 @@ namespace Avalonia.Gtk3
         {
             var evnt = (GdkEventKey*) pev;
             _lastKbdEvent = evnt->time;
-            if (Native.GtkImContextFilterKeypress(_imContext, pev))
-                return true;
             var e = new RawKeyEventArgs(
                 Gtk3Platform.Keyboard,
                 evnt->time,
                 evnt->type == GdkEventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
                 Avalonia.Gtk.Common.KeyTransform.ConvertKey((GdkKey)evnt->keyval), GetModifierKeys((GdkModifierType)evnt->state));
             OnInput(e);
+            if (Native.GtkImContextFilterKeypress(_imContext, pev))
+                return true;
             return true;
         }
 
@@ -265,6 +265,8 @@ namespace Avalonia.Gtk3
                 Paint?.Invoke(new Rect(ClientSize));
                 CurrentCairoContext = IntPtr.Zero;
             }
+            else
+                Paint?.Invoke(new Rect(ClientSize));
             return true;
         }
 
@@ -422,7 +424,7 @@ namespace Avalonia.Gtk3
             Native.GdkWindowSetCursor(Native.GtkWidgetGetWindow(GtkWidget), cursor?.Handle ??  IntPtr.Zero);
         }
 
-        public void Show() => Native.GtkWindowPresent(GtkWidget);
+        public virtual void Show() => Native.GtkWindowPresent(GtkWidget);
 
         public void Hide() => Native.GtkWidgetHide(GtkWidget);
 

+ 10 - 3
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@@ -63,11 +63,18 @@ namespace Avalonia.Gtk3
 
         public Action<WindowState> WindowStateChanged { get; set; }
 
-        public IDisposable ShowDialog()
+        public void ShowDialog(IWindowImpl parent)
         {
             Native.GtkWindowSetModal(GtkWidget, true);
-            Show();
-            return new EmptyDisposable();
+            Native.GtkWindowSetTransientFor(GtkWidget, ((WindowImpl)parent).GtkWidget.DangerousGetHandle());
+            Native.GtkWindowPresent(GtkWidget);
+        }
+
+        public override void Show()
+        {
+            Native.GtkWindowSetModal(GtkWidget, false);
+            Native.GtkWindowSetTransientFor(GtkWidget, IntPtr.Zero);
+            Native.GtkWindowPresent(GtkWidget);
         }
 
         public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(GtkWidget, enabled);

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs

@@ -1,6 +1,7 @@
 using System;
 using System.ComponentModel;
 using System.Collections.Generic;
+using System.Globalization;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Markup.Xaml.Converters;
@@ -36,6 +37,7 @@ namespace Avalonia.Markup.Xaml
             { typeof(Selector), typeof(SelectorTypeConverter) },
             { typeof(TimeSpan), typeof(TimeSpanTypeConverter) },
             { typeof(WindowIcon), typeof(IconTypeConverter) },
+            { typeof(CultureInfo), typeof(CultureInfoConverter)}
         };
 
         /// <summary>

+ 12 - 4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs

@@ -1,13 +1,12 @@
 // 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.
 
+using System;
 using Avalonia.Data;
+using Portable.Xaml.Markup;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions
 {
-    using Portable.Xaml.Markup;
-    using System;
-
     public class RelativeSourceExtension : MarkupExtension
     {
         public RelativeSourceExtension()
@@ -24,10 +23,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
             return new RelativeSource
             {
                 Mode = Mode,
+                AncestorType = AncestorType,
+                AncestorLevel = AncestorLevel,
+                Tree = Tree,
             };
         }
 
         [ConstructorArgument("mode")]
-        public RelativeSourceMode Mode { get; set; }
+        public RelativeSourceMode Mode { get; set; } = RelativeSourceMode.FindAncestor;
+
+        public Type AncestorType { get; set; }
+
+        public TreeType Tree { get; set; }
+
+        public int AncestorLevel { get; set; } = 1;
     }
 }

+ 5 - 3
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@@ -89,9 +89,11 @@ namespace Avalonia.Shared.PlatformSupport
 #if DEBUG
                 if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
                 {
-                    Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
-                                            + Environment.StackTrace
-                                            + "\n\nBlob created by " + _backtrace);
+                    lock(_lock)
+                        if (!IsDisposed)
+                            Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+                                                    + Environment.StackTrace
+                                                    + "\n\nBlob created by " + _backtrace);
                 }
 #endif
                 DoDispose();

+ 1 - 1
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -28,7 +28,7 @@ namespace Avalonia.Skia
                 var iface = display.Type == GlDisplayType.OpenGL2
                     ? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc))
                     : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc));
-                gl.ImmediateContext.MakeCurrent(null);
+                gl.ImmediateContext.MakeCurrent();
                 GrContext = GRContext.Create(GRBackend.OpenGL, iface);
             }
         }

+ 1 - 6
src/Windows/Avalonia.Win32/FramebufferManager.cs

@@ -5,7 +5,7 @@ using Avalonia.Win32.Interop;
 
 namespace Avalonia.Win32
 {
-    class FramebufferManager : IFramebufferPlatformSurface, IDisposable
+    class FramebufferManager : IFramebufferPlatformSurface
     {
         private readonly IntPtr _hwnd;
         private WindowFramebuffer _fb;
@@ -29,10 +29,5 @@ namespace Avalonia.Win32
             }
             return _fb;
         }
-
-        public void Dispose()
-        {
-            _fb?.Deallocate();
-        }
     }
 }

+ 15 - 3
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -780,8 +780,8 @@ namespace Avalonia.Win32.Interop
         [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
         private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value);
 
-        [DllImport("user32.dll", SetLastError = true)]
-        private static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value);
+        [DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLongPtr")]
+        private static extern IntPtr SetWindowLong64b(IntPtr hWnd, int nIndex, IntPtr value);
 
         public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value)
         {
@@ -791,7 +791,19 @@ namespace Avalonia.Win32.Interop
             }
             else
             {
-                return SetWindowLongPtr(hWnd, nIndex, value);
+                return (uint)SetWindowLong64b(hWnd, nIndex, new IntPtr((uint)value)).ToInt32();
+            }
+        }
+        
+        public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr handle)
+        {
+            if (IntPtr.Size == 4)
+            {
+                return new IntPtr(SetWindowLong32b(hWnd, nIndex, (uint)handle.ToInt32()));
+            }
+            else
+            {
+                return SetWindowLong64b(hWnd, nIndex, handle);
             }
         }
 

+ 5 - 4
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -27,11 +27,11 @@ namespace Avalonia
     {
         public static T UseWin32<T>(
             this T builder,
-            bool deferredRendering = true) 
+            bool deferredRendering = true, bool allowEgl = false) 
                 where T : AppBuilderBase<T>, new()
         {
             return builder.UseWindowingSubsystem(
-                () => Win32.Win32Platform.Initialize(deferredRendering),
+                () => Win32.Win32Platform.Initialize(deferredRendering, allowEgl),
                 "Win32");
         }
     }
@@ -66,7 +66,7 @@ namespace Avalonia.Win32
             Initialize(true);
         }
 
-        public static void Initialize(bool deferredRendering = true)
+        public static void Initialize(bool deferredRendering = true, bool allowEgl = false)
         {
             AvaloniaLocator.CurrentMutable
                 .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
@@ -80,7 +80,8 @@ namespace Avalonia.Win32
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
                 .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance);
-            Win32GlManager.Initialize();
+            if (allowEgl)
+                Win32GlManager.Initialize();
             UseDeferredRendering = deferredRendering;
             _uiThread = Thread.CurrentThread;
 

+ 24 - 10
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -13,6 +13,7 @@ using Avalonia.Input.Raw;
 using Avalonia.OpenGL;
 using Avalonia.Platform;
 using Avalonia.Rendering;
+using Avalonia.Threading;
 using Avalonia.Win32.Input;
 using Avalonia.Win32.Interop;
 using static Avalonia.Win32.Interop.UnmanagedMethods;
@@ -42,6 +43,8 @@ namespace Avalonia.Win32
         private OleDropTarget _dropTarget;
         private Size _minSize;
         private Size _maxSize;
+        private WindowImpl _parent;
+        private readonly List<WindowImpl> _disabledBy = new List<WindowImpl>();
 
 #if USE_MANAGED_DRAG
         private readonly ManagedWindowResizeDragHelper _managedDrag;
@@ -165,10 +168,10 @@ namespace Avalonia.Win32
             private set;
         }
 
-        public bool IsEnabled
+
+        void UpdateEnabled()
         {
-            get { return UnmanagedMethods.IsWindowEnabled(_hwnd); }
-            set { UnmanagedMethods.EnableWindow(_hwnd, value); }
+            EnableWindow(_hwnd, _disabledBy.Count == 0);
         }
 
         public Size MaxClientSize
@@ -232,8 +235,6 @@ namespace Avalonia.Win32
 
         public void Dispose()
         {
-            _framebuffer?.Dispose();
-            _framebuffer = null;
             if (_hwnd != IntPtr.Zero)
             {
                 UnmanagedMethods.DestroyWindow(_hwnd);
@@ -248,6 +249,12 @@ namespace Avalonia.Win32
 
         public void Hide()
         {
+            if (_parent != null)
+            {
+                _parent._disabledBy.Remove(this);
+                _parent.UpdateEnabled();
+                _parent = null;
+            }
             UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Hide);
         }
 
@@ -359,6 +366,7 @@ namespace Avalonia.Win32
 
         public virtual void Show()
         {
+            SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, IntPtr.Zero);
             ShowWindow(_showWindowState);
         }
 
@@ -412,11 +420,13 @@ namespace Avalonia.Win32
             }
         }
 
-        public virtual IDisposable ShowDialog()
+        public void ShowDialog(IWindowImpl parent)
         {
-            Show();
-
-            return Disposable.Empty;
+            _parent = (WindowImpl)parent;
+            _parent._disabledBy.Add(this);
+            _parent.UpdateEnabled();
+            SetWindowLongPtr(_hwnd, (int)WindowLongParam.GWL_HWNDPARENT, ((WindowImpl)parent)._hwnd);
+            ShowWindow(_showWindowState);
         }
 
         public void SetCursor(IPlatformHandle cursor)
@@ -490,6 +500,11 @@ namespace Avalonia.Win32
                     //Remove root reference to this class, so unmanaged delegate can be collected
                     s_instances.Remove(this);
                     Closed?.Invoke();
+                    if (_parent != null)
+                    {
+                        _parent._disabledBy.Remove(this);
+                        _parent.UpdateEnabled();
+                    }
                     //Free other resources
                     Dispose();
                     return IntPtr.Zero;
@@ -633,7 +648,6 @@ namespace Avalonia.Win32
 
                 case UnmanagedMethods.WindowsMessage.WM_PAINT:
                     UnmanagedMethods.PAINTSTRUCT ps;
-
                     if (UnmanagedMethods.BeginPaint(_hwnd, out ps) != IntPtr.Zero)
                     {
                         var f = Scaling;

+ 14 - 0
tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs

@@ -0,0 +1,14 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Platform;
+
+namespace Avalonia.Controls.UnitTests
+{
+    public class CursorFactoryMock : IStandardCursorFactory
+    {
+        public IPlatformHandle GetCursor(StandardCursorType cursorType)
+        {
+            return new PlatformHandle(IntPtr.Zero, cursorType.ToString());
+        }
+    }
+}

+ 284 - 0
tests/Avalonia.Controls.UnitTests/SharedSizeScopeTests.cs

@@ -0,0 +1,284 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Platform;
+using Avalonia.UnitTests;
+
+using Moq;
+
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests
+{
+    public class SharedSizeScopeTests
+    {
+        public SharedSizeScopeTests()
+        {
+        }
+
+        [Fact]
+        public void All_Descendant_Grids_Are_Registered_When_Added_After_Setting_Scope()
+        {
+            var grids = new[] { new Grid(), new Grid(), new Grid() };
+            var scope = new Panel();
+            scope.Children.AddRange(grids);
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = scope;
+
+            Assert.All(grids, g => Assert.True(g.HasSharedSizeScope()));
+        }
+
+        [Fact]
+        public void All_Descendant_Grids_Are_Registered_When_Setting_Scope()
+        {
+            var grids = new[] { new Grid(), new Grid(), new Grid() };
+            var scope = new Panel();
+            scope.Children.AddRange(grids);
+
+            var root = new TestRoot();
+            root.Child = scope;
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+
+            Assert.All(grids, g => Assert.True(g.HasSharedSizeScope()));
+        }
+
+        [Fact]
+        public void All_Descendant_Grids_Are_Unregistered_When_Resetting_Scope()
+        {
+            var grids = new[] { new Grid(), new Grid(), new Grid() };
+            var scope = new Panel();
+            scope.Children.AddRange(grids);
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = scope;
+
+            Assert.All(grids, g => Assert.True(g.HasSharedSizeScope()));
+            root.SetValue(Grid.IsSharedSizeScopeProperty, false);
+            Assert.All(grids, g => Assert.False(g.HasSharedSizeScope()));
+            Assert.Equal(null, root.GetValue(Grid.s_sharedSizeScopeHostProperty));
+        }
+
+        [Fact]
+        public void Size_Is_Propagated_Between_Grids()
+        {
+            var grids = new[] { CreateGrid("A", null),CreateGrid(("A",new GridLength(30)), (null, new GridLength()))};
+            var scope = new Panel();
+            scope.Children.AddRange(grids);
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = scope;
+
+            root.Measure(new Size(50, 50));
+            root.Arrange(new Rect(new Point(), new Point(50, 50)));
+            Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth);
+        }
+
+        [Fact]
+        public void Size_Propagation_Is_Constrained_To_Innermost_Scope()
+        {
+            var grids = new[] { CreateGrid("A", null), CreateGrid(("A", new GridLength(30)), (null, new GridLength())) };
+            var innerScope = new Panel();
+            innerScope.Children.AddRange(grids);
+            innerScope.SetValue(Grid.IsSharedSizeScopeProperty, true);
+
+            var outerGrid = CreateGrid(("A", new GridLength(0)));
+            var outerScope = new Panel();
+            outerScope.Children.AddRange(new[] { outerGrid, innerScope });
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = outerScope;
+
+            root.Measure(new Size(50, 50));
+            root.Arrange(new Rect(new Point(), new Point(50, 50)));
+            Assert.Equal(0, outerGrid.ColumnDefinitions[0].ActualWidth);
+        }
+
+        [Fact]
+        public void Size_Is_Propagated_Between_Rows_And_Columns()
+        {
+            var grid = new Grid
+            {
+                ColumnDefinitions = new ColumnDefinitions("*,30"),
+                RowDefinitions = new RowDefinitions("*,10")
+            };
+
+            grid.ColumnDefinitions[1].SharedSizeGroup = "A";
+            grid.RowDefinitions[1].SharedSizeGroup = "A";
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = grid;
+
+            root.Measure(new Size(50, 50));
+            root.Arrange(new Rect(new Point(), new Point(50, 50)));
+            Assert.Equal(30, grid.RowDefinitions[1].ActualHeight);
+        }
+
+        [Fact]
+        public void Size_Group_Changes_Are_Tracked()
+        {
+            var grids = new[] {
+                CreateGrid((null, new GridLength(0, GridUnitType.Auto)), (null, new GridLength())),
+                CreateGrid(("A", new GridLength(30)), (null, new GridLength())) };
+            var scope = new Panel();
+            scope.Children.AddRange(grids);
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = scope;
+
+            root.Measure(new Size(50, 50));
+            root.Arrange(new Rect(new Point(), new Point(50, 50)));
+            Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth);
+
+            grids[0].ColumnDefinitions[0].SharedSizeGroup = "A";
+
+            root.Measure(new Size(51, 51));
+            root.Arrange(new Rect(new Point(), new Point(51, 51)));
+            Assert.Equal(30, grids[0].ColumnDefinitions[0].ActualWidth);
+
+            grids[0].ColumnDefinitions[0].SharedSizeGroup = null;
+
+            root.Measure(new Size(52, 52));
+            root.Arrange(new Rect(new Point(), new Point(52, 52)));
+            Assert.Equal(0, grids[0].ColumnDefinitions[0].ActualWidth);
+        }
+
+        [Fact]
+        public void Collection_Changes_Are_Tracked()
+        {
+            var grid = CreateGrid(
+                ("A", new GridLength(20)),
+                ("A", new GridLength(30)),
+                ("A", new GridLength(40)),
+                (null, new GridLength()));
+
+            var scope = new Panel();
+            scope.Children.Add(grid);
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = scope;
+
+            grid.Measure(new Size(200, 200));
+            grid.Arrange(new Rect(new Point(), new Point(200, 200)));
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(40, cd.ActualWidth));
+
+            grid.ColumnDefinitions.RemoveAt(2);
+
+            grid.Measure(new Size(200, 200));
+            grid.Arrange(new Rect(new Point(), new Point(200, 200)));
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth));
+
+            grid.ColumnDefinitions.Insert(1, new ColumnDefinition { Width = new GridLength(35), SharedSizeGroup = "A" });
+
+            grid.Measure(new Size(200, 200));
+            grid.Arrange(new Rect(new Point(), new Point(200, 200)));
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(35, cd.ActualWidth));
+
+            grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(10), SharedSizeGroup = "A" };
+
+            grid.Measure(new Size(200, 200));
+            grid.Arrange(new Rect(new Point(), new Point(200, 200)));
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(30, cd.ActualWidth));
+
+            grid.ColumnDefinitions[1] = new ColumnDefinition { Width = new GridLength(50), SharedSizeGroup = "A" };
+
+            grid.Measure(new Size(200, 200));
+            grid.Arrange(new Rect(new Point(), new Point(200, 200)));
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(50, cd.ActualWidth));
+        }
+
+        [Fact]
+        public void Size_Priorities_Are_Maintained()
+        {
+            var sizers = new List<Control>();
+            var grid = CreateGrid(
+                ("A", new GridLength(20)), 
+                ("A", new GridLength(20, GridUnitType.Auto)), 
+                ("A", new GridLength(1, GridUnitType.Star)), 
+                ("A", new GridLength(1, GridUnitType.Star)),
+                (null, new GridLength()));
+            for (int i = 0; i < 3; i++)
+                sizers.Add(AddSizer(grid, i, 6 + i * 6));
+            var scope = new Panel();
+            scope.Children.Add(grid);
+
+            var root = new TestRoot();
+            root.SetValue(Grid.IsSharedSizeScopeProperty, true);
+            root.Child = scope;
+
+            grid.Measure(new Size(100, 100));
+            grid.Arrange(new Rect(new Point(), new Point(100, 100)));
+            // all in group are equal to the first fixed column
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(20, cd.ActualWidth));
+
+            grid.ColumnDefinitions[0].SharedSizeGroup = null;
+
+            grid.Measure(new Size(100, 100));
+            grid.Arrange(new Rect(new Point(), new Point(100, 100)));
+            // all in group are equal to width (MinWidth) of the sizer in the second column
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 1 * 6, cd.ActualWidth));
+
+            grid.ColumnDefinitions[1].SharedSizeGroup = null;
+
+            grid.Measure(new Size(double.PositiveInfinity, 100));
+            grid.Arrange(new Rect(new Point(), new Point(100, 100)));
+            // with no constraint star columns default to the MinWidth of the sizer in the column
+            Assert.All(grid.ColumnDefinitions.Where(cd => cd.SharedSizeGroup == "A"), cd => Assert.Equal(6 + 2 * 6, cd.ActualWidth));
+        }
+
+        // grid creators
+        private Grid CreateGrid(params string[] columnGroups)
+        {
+            return CreateGrid(columnGroups.Select(s => (s, ColumnDefinition.WidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray());
+        }
+
+        private Grid CreateGrid(params (string name, GridLength width)[] columns)
+        {
+            return CreateGrid(columns.Select(c =>
+                (c.name, c.width, ColumnDefinition.MinWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray());
+        }
+
+        private Grid CreateGrid(params (string name, GridLength width, double minWidth)[] columns)
+        {
+            return CreateGrid(columns.Select(c =>
+                (c.name, c.width, c.minWidth, ColumnDefinition.MaxWidthProperty.GetDefaultValue(typeof(ColumnDefinition)))).ToArray());
+        }
+
+        private Grid CreateGrid(params (string name, GridLength width, double minWidth, double maxWidth)[] columns)
+        {
+            var columnDefinitions = new ColumnDefinitions();
+
+            columnDefinitions.AddRange(
+                    columns.Select(c => new ColumnDefinition
+                    {
+                        SharedSizeGroup = c.name,
+                        Width = c.width,
+                        MinWidth = c.minWidth,
+                        MaxWidth = c.maxWidth
+                    })
+                );
+            var grid = new Grid
+            {
+                ColumnDefinitions = columnDefinitions
+            };
+
+            return grid;
+        }
+
+        private Control AddSizer(Grid grid, int column, double size = 30)
+        {
+            var ctrl = new Control {  MinWidth = size, MinHeight = size };
+            ctrl.SetValue(Grid.ColumnProperty,column);
+            grid.Children.Add(ctrl);
+            return ctrl;
+        }
+    }
+}

+ 68 - 77
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests
             TabItem selected;
             var target = new TabControl
             {
-                Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
+                Template = TabControlTemplate(),
                 Items = new[]
                 {
                     (selected = new TabItem
@@ -61,7 +61,7 @@ namespace Avalonia.Controls.UnitTests
 
             var target = new TabControl
             {
-                Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
+                Template = TabControlTemplate(),
                 Items = items,
             };
 
@@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
 
             var target = new TabControl
             {
-                Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
+                Template = TabControlTemplate(),
                 Items = collection,
             };
 
@@ -147,7 +147,7 @@ namespace Avalonia.Controls.UnitTests
                     },
                     Child = new TabControl
                     {
-                        Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
+                        Template = TabControlTemplate(),
                         Items = collection,
                     }
                 };
@@ -172,7 +172,7 @@ namespace Avalonia.Controls.UnitTests
 
             var target = new TabControl
             {
-                Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
+                Template = TabControlTemplate(),
                 DataContext = "Base",
                 DataTemplates =
                 {
@@ -182,41 +182,39 @@ namespace Avalonia.Controls.UnitTests
             };
 
             ApplyTemplate(target);
-            var carousel = (Carousel)target.Pages;
 
-            var container = (ContentPresenter)carousel.Presenter.Panel.Children.Single();
-            container.UpdateChild();
-            var dataContext = ((TextBlock)container.Child).DataContext;
+            target.ContentPart.UpdateChild();
+            var dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
             Assert.Equal(items[0], dataContext);
 
             target.SelectedIndex = 1;
-            container = (ContentPresenter)carousel.Presenter.Panel.Children.Single();
-            container.UpdateChild();
-            dataContext = ((Button)container.Child).DataContext;
+            target.ContentPart.UpdateChild();
+            dataContext = ((Button)target.ContentPart.Child).DataContext;
             Assert.Equal(items[1], dataContext);
 
             target.SelectedIndex = 2;
-            dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext;
+            target.ContentPart.UpdateChild();
+            dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
             Assert.Equal("Base", dataContext);
 
             target.SelectedIndex = 3;
-            container = (ContentPresenter)carousel.Presenter.Panel.Children[0];
-            container.UpdateChild();
-            dataContext = ((TextBlock)container.Child).DataContext;
+            target.ContentPart.UpdateChild();
+            dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
             Assert.Equal("Qux", dataContext);
 
             target.SelectedIndex = 4;
-            dataContext = ((TextBlock)carousel.Presenter.Panel.Children.Single()).DataContext;
+            target.ContentPart.UpdateChild();
+            dataContext = target.ContentPart.DataContext;
             Assert.Equal("Base", dataContext);
         }
 
         /// <summary>
-        /// Non-headered control items should result in TabStripItems with empty content.
+        /// Non-headered control items should result in TabItems with empty header.
         /// </summary>
         /// <remarks>
-        /// If a TabStrip is created with non IHeadered controls as its items, don't try to
-        /// display the control in the TabStripItem: if the TabStrip is part of a TabControl
-        /// then *that* will also try to display the control, resulting in dual-parentage 
+        /// If a TabControl is created with non IHeadered controls as its items, don't try to
+        /// display the control in the header: if the control is part of the header then
+        /// *that* control would also end up in the content region, resulting in dual-parentage 
         /// breakage.
         /// </remarks>
         [Fact]
@@ -230,18 +228,20 @@ namespace Avalonia.Controls.UnitTests
 
             var target = new TabControl
             {
-                Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
+                Template = TabControlTemplate(),
                 Items = items,
             };
 
             ApplyTemplate(target);
 
-            var result = target.TabStrip.GetLogicalChildren()
-                .OfType<TabStripItem>()
-                .Select(x => x.Content)
+            var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren();
+
+            var result = logicalChildren
+                .OfType<TabItem>()
+                .Select(x => x.Header)
                 .ToList();
 
-            Assert.Equal(new object[] { string.Empty, string.Empty }, result);
+            Assert.Equal(new object[] { null, null }, result);
         }
 
         [Fact]
@@ -249,7 +249,7 @@ namespace Avalonia.Controls.UnitTests
         {
             TabControl target = new TabControl
             {
-                Template = new FuncControlTemplate<TabControl>(CreateTabControlTemplate),
+                Template = TabControlTemplate(),
                 Items = new[]
                 {
                     new TabItem { Header = "Foo" },
@@ -262,70 +262,61 @@ namespace Avalonia.Controls.UnitTests
 
             target.SelectedIndex = 2;
 
-            var carousel = (Carousel)target.Pages;
-            var page = (TabItem)carousel.SelectedItem;
+            var page = (TabItem)target.SelectedItem;
 
             Assert.Null(page.Content);
         }
 
-        private Control CreateTabControlTemplate(TabControl parent)
+        private IControlTemplate TabControlTemplate()
         {
-            return new StackPanel
-            {
-                Children =
-                {
-                    new TabStrip
-                    {
-                        Name = "PART_TabStrip",
-                        Template = new FuncControlTemplate<TabStrip>(CreateTabStripTemplate),
-                        MemberSelector = TabControl.HeaderSelector,
-                        [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
-                        [!!TabStrip.SelectedIndexProperty] = parent[!!TabControl.SelectedIndexProperty]
-                    },
-                    new Carousel
-                    {
-                        Name = "PART_Content",
-                        Template = new FuncControlTemplate<Carousel>(CreateCarouselTemplate),
-                        MemberSelector = TabControl.ContentSelector,
-                        [!Carousel.ItemsProperty] = parent[!TabControl.ItemsProperty],
-                        [!Carousel.SelectedItemProperty] = parent[!TabControl.SelectedItemProperty],
-                    }
-                }
-            };
-        }
+            return new FuncControlTemplate<TabControl>(parent =>
 
-        private Control CreateTabStripTemplate(TabStrip parent)
-        {
-            return new ItemsPresenter
-            {
-                Name = "PART_ItemsPresenter",
-                [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
-                [!CarouselPresenter.MemberSelectorProperty] = parent[!ItemsControl.MemberSelectorProperty],
-            };
+                new StackPanel
+                {
+                    Children = {
+                                   new ItemsPresenter
+                                   {
+                                       Name = "PART_ItemsPresenter",
+                                       [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty],
+                                       [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty],
+                                   },
+                                   new ContentPresenter
+                                   {
+                                       Name = "PART_Content",
+                                       [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty],
+                                       [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty],
+                                   }
+                               }
+                });
         }
 
-        private Control CreateCarouselTemplate(Carousel control)
+        private IControlTemplate TabItemTemplate()
         {
-            return new CarouselPresenter
-            {
-                Name = "PART_ItemsPresenter",
-                [!CarouselPresenter.ItemsProperty] = control[!ItemsControl.ItemsProperty],
-                [!CarouselPresenter.ItemsPanelProperty] = control[!ItemsControl.ItemsPanelProperty],
-                [!CarouselPresenter.MemberSelectorProperty] = control[!ItemsControl.MemberSelectorProperty],
-                [!CarouselPresenter.SelectedIndexProperty] = control[!SelectingItemsControl.SelectedIndexProperty],
-                [~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
-            };
+            return new FuncControlTemplate<TabItem>(parent =>
+                new ContentPresenter
+                {
+                    Name = "PART_ContentPresenter",
+                    [!ContentPresenter.ContentProperty] = parent[!TabItem.HeaderProperty],
+                    [!ContentPresenter.ContentTemplateProperty] = parent[!TabItem.HeaderTemplateProperty]
+                });
         }
 
         private void ApplyTemplate(TabControl target)
         {
             target.ApplyTemplate();
-            var carousel = (Carousel)target.Pages;
-            carousel.ApplyTemplate();
-            carousel.Presenter.ApplyTemplate();
-            var tabStrip = (TabStrip)target.TabStrip;
-            tabStrip.ApplyTemplate();
-            tabStrip.Presenter.ApplyTemplate();
+
+            target.Presenter.ApplyTemplate();
+
+            foreach (var tabItem in target.GetLogicalChildren().OfType<TabItem>())
+            {
+                tabItem.Template = TabItemTemplate();
+
+                tabItem.ApplyTemplate();
+
+                ((ContentPresenter)tabItem.Presenter).UpdateChild();
+            }
+
+            target.ContentPart.ApplyTemplate();
         }
 
         private class Item

+ 5 - 2
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@@ -62,9 +62,11 @@ namespace Avalonia.Controls.UnitTests
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
             {
+                var parent = new Window();
+                parent.Show();
                 var window = new Window();
 
-                var task = window.ShowDialog();
+                var task = window.ShowDialog(parent);
 
                 Assert.True(window.IsVisible);
             }
@@ -258,12 +260,13 @@ namespace Avalonia.Controls.UnitTests
         {
             using (UnitTestApplication.Start(TestServices.StyledWindow))
             {
+                var parent = new Mock<IWindowImpl>();
                 var windowImpl = new Mock<IWindowImpl>();
                 windowImpl.SetupProperty(x => x.Closed);
                 windowImpl.Setup(x => x.Scaling).Returns(1);
 
                 var target = new Window(windowImpl.Object);
-                var task = target.ShowDialog<bool>();
+                var task = target.ShowDialog<bool>(parent.Object);
 
                 windowImpl.Object.Closed();
 

+ 4 - 0
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@@ -21,6 +21,7 @@ using Xunit;
 using Avalonia.Media;
 using System;
 using System.Collections.Generic;
+using Avalonia.Controls.UnitTests;
 using Avalonia.UnitTests;
 
 namespace Avalonia.Layout.UnitTests
@@ -98,6 +99,8 @@ namespace Avalonia.Layout.UnitTests
                     }
                 };
 
+                window.Resources["ScrollBarThickness"] = 10.0;
+
                 window.Show();
                 window.LayoutManager.ExecuteInitialLayoutPass(window);
 
@@ -197,6 +200,7 @@ namespace Avalonia.Layout.UnitTests
             windowImpl.SetupGet(x => x.Scaling).Returns(1);
 
             AvaloniaLocator.CurrentMutable
+                .Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryMock())
                 .Bind<IAssetLoader>().ToConstant(new AssetLoader())
                 .Bind<IInputManager>().ToConstant(new Mock<IInputManager>().Object)
                 .Bind<IGlobalStyles>().ToConstant(globalStyles.Object)

+ 99 - 1
tests/Avalonia.Markup.UnitTests/Data/BindingTests_RelativeSource.cs

@@ -1,9 +1,13 @@
 // 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.
 
+using System;
+using System.Collections.Generic;
+using System.Reactive.Subjects;
 using Avalonia.Controls;
 using Avalonia.Data;
-using Avalonia.Markup.Data;
+using Avalonia.Data.Core;
+using Avalonia.Markup.Parsers;
 using Avalonia.UnitTests;
 using Xunit;
 
@@ -162,5 +166,99 @@ namespace Avalonia.Markup.UnitTests.Data
             decorator2.Child = target;
             Assert.Equal("decorator2", target.Text);
         }
+
+        [Fact]
+        public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_BindingPath()
+        {
+            TextBlock target;
+            Decorator decorator1;
+            Decorator decorator2;
+
+            var viewModel = new { Value = "Foo" };
+
+            var root1 = new TestRoot
+            {
+                Child = decorator1 = new Decorator
+                {
+                    Name = "decorator1",
+                    Child = target = new TextBlock(),
+                },
+                DataContext = viewModel
+            };
+
+            var root2 = new TestRoot
+            {
+                Child = decorator2 = new Decorator
+                {
+                    Name = "decorator2",
+                },
+                DataContext = viewModel
+            };
+
+            var binding = new Binding
+            {
+                Path = "DataContext.Value",
+                RelativeSource = new RelativeSource
+                {
+                    AncestorType = typeof(Decorator),
+                }
+            };
+
+            target.Bind(TextBox.TextProperty, binding);
+            Assert.Equal("Foo", target.Text);
+
+            decorator1.Child = null;
+            Assert.Null(target.Text);
+
+            decorator2.Child = target;
+            Assert.Equal("Foo", target.Text);
+        }
+
+        [Fact]
+        public void Should_Update_When_Detached_And_Attached_To_Visual_Tree_With_ComplexBindingPath()
+        {
+            TextBlock target;
+            Decorator decorator1;
+            Decorator decorator2;
+
+            var vm = new { Foo = new  { Value = "Foo" } };
+
+            var root1 = new TestRoot
+            {
+                Child = decorator1 = new Decorator
+                {
+                    Name = "decorator1",
+                    Child = target = new TextBlock(),
+                },
+                DataContext = vm
+            };
+
+            var root2 = new TestRoot
+            {
+                Child = decorator2 = new Decorator
+                {
+                    Name = "decorator2",
+                },
+                DataContext = vm
+            };
+
+            var binding = new Binding
+            {
+                Path = "DataContext.Foo.Value",
+                RelativeSource = new RelativeSource
+                {
+                    AncestorType = typeof(Decorator),
+                }
+            };
+
+            target.Bind(TextBox.TextProperty, binding);
+            Assert.Equal("Foo", target.Text);
+
+            decorator1.Child = null;
+            Assert.Null(target.Text);
+
+            decorator2.Child = target;
+            Assert.Equal("Foo", target.Text);
+        }
     }
 }

+ 54 - 4
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs

@@ -1,10 +1,10 @@
-using Avalonia.Data;
-using Avalonia.Markup.Parsers;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Reactive.Linq;
-using System.Text;
+using System.Reactive.Subjects;
 using System.Threading.Tasks;
+using Avalonia.Data;
+using Avalonia.Markup.Parsers;
 using Xunit;
 
 namespace Avalonia.Markup.UnitTests.Parsers
@@ -38,5 +38,55 @@ namespace Avalonia.Markup.UnitTests.Parsers
 
             GC.KeepAlive(data);
         }
+
+        [Fact]
+        public void Should_Update_Value_After_Root_Changes()
+        {
+            var root = new { DataContext = new { Value = "Foo" } };
+            var subject = new Subject<object>();
+            var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Value");
+
+            var values = new List<object>();
+            obs.Subscribe(v => values.Add(v));
+
+            subject.OnNext(root);
+            subject.OnNext(null);
+            subject.OnNext(root);
+
+            Assert.Equal("Foo", values[0]);
+
+            Assert.IsType<BindingNotification>(values[1]);
+            var bn = values[1] as BindingNotification;
+            Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value);
+            Assert.Equal(BindingErrorType.Error, bn.ErrorType);
+
+            Assert.Equal(3, values.Count);
+            Assert.Equal("Foo", values[2]);
+        }
+
+        [Fact]
+        public void Should_Update_Value_After_Root_Changes_With_ComplexPath()
+        {
+            var root = new { DataContext = new { Foo = new { Value = "Foo" } } };
+            var subject = new Subject<object>();
+            var obs = ExpressionObserverBuilder.Build(subject, "DataContext.Foo.Value");
+
+            var values = new List<object>();
+            obs.Subscribe(v => values.Add(v));
+
+            subject.OnNext(root);
+            subject.OnNext(null);
+            subject.OnNext(root);
+
+            Assert.Equal("Foo", values[0]);
+
+            Assert.IsType<BindingNotification>(values[1]);
+            var bn = values[1] as BindingNotification;
+            Assert.Equal(AvaloniaProperty.UnsetValue, bn.Value);
+            Assert.Equal(BindingErrorType.Error, bn.ErrorType);
+
+            Assert.Equal(3, values.Count);
+            Assert.Equal("Foo", values[2]);
+        }
     }
 }

+ 3 - 1
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

@@ -405,7 +405,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                 root.Renderer = new DeferredRenderer(root, null);
                 root.Measure(Size.Infinity);
                 root.Arrange(new Rect(container.DesiredSize));
-
+                
+                root.Renderer.Paint(Rect.Empty);
                 var result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
 
                 Assert.Equal(item1, result);
@@ -421,6 +422,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
                 container.InvalidateArrange();
                 container.Arrange(new Rect(container.DesiredSize));
 
+                root.Renderer.Paint(Rect.Empty);
                 result = root.Renderer.HitTest(new Point(50, 150), root, null).First();
                 Assert.Equal(item2, result);