question

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 asked EmonHaque-1485 commented

How do you handle multiple windows with different threads?

In an empty WPF Project first I've deleted everything except AssemblyInfo.cs, then added App.cs, where I start the app, and four other windows. RootWindow acts as the positioner/resizer of three other windows, TopWindow, MiddleWindow and BottomWindow. These three windows are identical, all they have is a constructor with these:

 class MiddleWindow : Window
 {
     public MiddleWindow() {
         Title = "Middle";
         ResizeMode = ResizeMode.NoResize;
         AllowsTransparency = true;
         WindowStyle = WindowStyle.None;
         ShowInTaskbar = false;
     }
 }

in the RootWindow, I've three Rectangle, topRect, midRect, botRect, and in its ArrangeOverride I set the Top, Left, Width and Height of those three Windows based on the position of those Rectangles. Here's what happens when I run the app:

107284-test.gif

initially there's always a gap between the MiddleWindow and BottomWindow, As I resize the RootWindow those windows resizes sometimes with the gap and other times without gap. If I maximize and restore the RootWindow, those three also resize accordingly and when I move the RootWindow, they also move along. The problem arises when I click on any of those three windows or minimize the RootWindow. If I click on any of those three, all three starts blinking and all of them disappears eventually!

In the OnDeactivated of the RootWindow, I hide those so they become invisible and in the OnActivated of the RootWindow, I set Visibility of those to Visible BUT only the last one appears when I restore the RootWindow by clicking on taskbar!

Here's the RootWindow.cs:

 class RootWindow : Window
 {
     double radius = 5;
     Border windowBorder, titleBar;
     Grid contentGrid, titlebarIconGrid, rectGrid;
     ActionButton close, minimize, maxRestore;
     Rectangle topRect, midRect, botRect;
     public TopWindow TopWin { get; set; }
     public MiddleWindow MidWin { get; set; }
     public BottomWindow BotWin { get; set; }
     public RootWindow() {
         Height = 800;
         Width = 1200;
         WindowStartupLocation = WindowStartupLocation.CenterScreen;
         WindowStyle = WindowStyle.None;
         AllowsTransparency = true;
         WindowChrome.SetWindowChrome(this, new WindowChrome() {
             ResizeBorderThickness = new Thickness(0, 0, 5, 5),
             CaptionHeight = 0
         });
         addTitleIcons();
         titleBar = new Border() {
             CornerRadius = new CornerRadius(radius, radius, 0, 0),
             Background = Brushes.LightGray,
             Height = 32,
             Effect = new DropShadowEffect() { BlurRadius = 5, Opacity = 0.5, Direction = -90 },
             Child = titlebarIconGrid
         };
         initRects();
         Grid.SetRow(rectGrid, 1);
         contentGrid = new Grid() {
             RowDefinitions = {
                 new RowDefinition() { Height = GridLength.Auto },
                 new RowDefinition()
             },
             Children = { titleBar, rectGrid }
         };
         windowBorder = new Border() {
             Background = Brushes.Transparent,
             CornerRadius = new CornerRadius(radius),
             BorderThickness = new Thickness(1),
             BorderBrush = Brushes.LightBlue,
             Child = contentGrid
         };
         AddVisualChild(windowBorder);
         titleBar.MouseLeftButtonDown += handleResize;
         titleBar.MouseMove += move;
         Loaded += onLoaded;
     }
     void onLoaded(object sender, RoutedEventArgs e) {
         TopWin.Dispatcher.Invoke(TopWin.Show);
         MidWin.Dispatcher.Invoke(MidWin.Show);
         BotWin.Dispatcher.Invoke(BotWin.Show);
     }
     void initRects() {
         topRect = new Rectangle()/* { IsHitTestVisible = false }*/;
         midRect = new Rectangle()/* { IsHitTestVisible = false }*/;
         botRect = new Rectangle()/* { IsHitTestVisible = false }*/;
         Grid.SetRow(midRect, 1);
         Grid.SetRow(botRect, 2);
         rectGrid = new Grid() {
             IsHitTestVisible = false,
             RowDefinitions = {
                 new RowDefinition(),
                 new RowDefinition(),
                 new RowDefinition()
             },
             Children = { topRect, midRect, botRect }
         };
     }
     void move(object sender, MouseEventArgs e) {
         if (e.LeftButton == MouseButtonState.Pressed) {
             DragMove();
             InvalidateArrange();
         }
     }
     void handleResize(object sender, MouseButtonEventArgs e) {
         if (e.ClickCount == 2) resize();
     }
     void addTitleIcons() {
         close = new ActionButton() {
             Width = 24,
             Height = 24,
             ToolTip = "Close",
             Margin = new Thickness(0, 0, 5, 0),
             Icon = Icons.CloseCircle,
             Command = Application.Current.Shutdown
         };
         maxRestore = new ActionButton() {
             Width = 18,
             Height = 18,
             ToolTip = "Maximize",
             Margin = new Thickness(0, 0, 5, 0),
             Icon = Icons.Maximize,
             Command = resize
         };
         minimize = new ActionButton() {
             Width = 18,
             Height = 18,
             ToolTip = "Minimize",
             Margin = new Thickness(0, 0, 5, 0),
             Icon = Icons.Minimize,
             Command = () => WindowState = WindowState.Minimized
         };
         Grid.SetColumn(close, 3);
         Grid.SetColumn(maxRestore, 2);
         Grid.SetColumn(minimize, 1);
         titlebarIconGrid = new Grid() {
             ColumnDefinitions = {
                 new ColumnDefinition(),
                 new ColumnDefinition(){ Width = GridLength.Auto },
                 new ColumnDefinition(){ Width = GridLength.Auto },
                 new ColumnDefinition(){ Width = GridLength.Auto }
             },
             Children = { close, maxRestore, minimize }
         };
     }
     void resize() {
         if (WindowState == WindowState.Maximized) {
             ResizeMode = ResizeMode.CanResizeWithGrip;
             WindowState = WindowState.Normal;
             maxRestore.Icon = Icons.Maximize;
             maxRestore.ToolTip = "Maximize";
         }
         else {
             ResizeMode = ResizeMode.NoResize;
             WindowState = WindowState.Maximized;
             maxRestore.Icon = Icons.Restore;
             maxRestore.ToolTip = "Restore";
         }
     }

     protected override Size ArrangeOverride(Size arrangeBounds) {
         windowBorder.Width = arrangeBounds.Width;
         windowBorder.Height = arrangeBounds.Height;
         windowBorder.Measure(arrangeBounds);
         windowBorder.Arrange(new Rect(windowBorder.DesiredSize));
         double width = topRect.ActualWidth;
         double height = topRect.ActualHeight;
         var topPoint = topRect.PointToScreen(new Point(0, 0));
         var midPoint = midRect.PointToScreen(new Point(0, 0));
         var botPoint = botRect.PointToScreen(new Point(0, 0));
         TopWin.Dispatcher.Invoke(() => {
             TopWin.Left = topPoint.X;
             TopWin.Top = topPoint.Y;
             TopWin.Width = width;
             TopWin.Height = height;
         });
         MidWin.Dispatcher.Invoke(() => {
             MidWin.Left = midPoint.X;
             MidWin.Top = midPoint.Y;
             MidWin.Width = width;
             MidWin.Height = height;
         });
         BotWin.Dispatcher.Invoke(() => {
             BotWin.Left = botPoint.X;
             BotWin.Top = botPoint.Y;
             BotWin.Width = width;
             BotWin.Height = height;
         });
         return windowBorder.DesiredSize;
     }
     protected override void OnActivated(EventArgs e) {
         TopWin.Dispatcher.Invoke(() => TopWin.Visibility = Visibility.Visible);
         MidWin.Dispatcher.Invoke(() => MidWin.Visibility = Visibility.Visible);
         BotWin.Dispatcher.Invoke(() => BotWin.Visibility = Visibility.Visible);
         //TopWin.Dispatcher.Invoke(TopWin.Show);
         //MidWin.Dispatcher.Invoke(MidWin.Show);
         //BotWin.Dispatcher.Invoke(BotWin.Show);
         //TopWin.Dispatcher.Invoke(() => TopWin.WindowState = WindowState.Normal);
         //MidWin.Dispatcher.Invoke(() => MidWin.WindowState = WindowState.Normal);
         //BotWin.Dispatcher.Invoke(() => BotWin.WindowState = WindowState.Normal);
         //InvalidateArrange();
         InvalidateVisual();
     }
     protected override void OnDeactivated(EventArgs e) {
         TopWin.Dispatcher.Invoke(() => TopWin.Visibility = Visibility.Collapsed);
         MidWin.Dispatcher.Invoke(() => MidWin.Visibility = Visibility.Collapsed);
         BotWin.Dispatcher.Invoke(() => BotWin.Visibility = Visibility.Collapsed);
         //TopWin.Dispatcher.Invoke(TopWin.Hide);
         //MidWin.Dispatcher.Invoke(MidWin.Hide);
         //BotWin.Dispatcher.Invoke(BotWin.Hide);
         //TopWin.Dispatcher.Invoke(() => TopWin.WindowState = WindowState.Minimized);
         //MidWin.Dispatcher.Invoke(() => MidWin.WindowState = WindowState.Minimized);
         //BotWin.Dispatcher.Invoke(() => BotWin.WindowState = WindowState.Minimized);
     }
     protected override void OnClosed(EventArgs e) {
         TopWin.Dispatcher.Invoke(TopWin.Close);
         MidWin.Dispatcher.Invoke(MidWin.Close);
         BotWin.Dispatcher.Invoke(BotWin.Close);
         TopWin.Dispatcher.InvokeShutdown();
         MidWin.Dispatcher.InvokeShutdown();
         BotWin.Dispatcher.InvokeShutdown();
         App.Current.Shutdown();
     }
     protected override Visual GetVisualChild(int index) => windowBorder;
     protected override int VisualChildrenCount => 1;
     protected override void OnClosing(CancelEventArgs e) {
         titleBar.MouseLeftButtonDown -= handleResize;
         titleBar.MouseMove -= move;
     }
 }

and here's the App.cs:

 class App : Application
 {
     [STAThread]
     static void Main() => new App().Run();
     protected override void OnStartup(StartupEventArgs e) {
         TopWindow top = null;
         MiddleWindow mid = null;
         BottomWindow bot = null;
         var thread1 = new Thread(() => { 
             top = new TopWindow() {
                 Content = new TextBlock() { 
                     Text = "Top Window",
                     HorizontalAlignment = HorizontalAlignment.Center,
                     VerticalAlignment = VerticalAlignment.Center,
                     FontSize = 36
                 } 
             }; 
             Dispatcher.Run(); 
         });
         var thread2 = new Thread(() => { 
             mid = new MiddleWindow() {
                 Content = new TextBlock() {
                     Text = "Middle Window",
                     HorizontalAlignment = HorizontalAlignment.Center,
                     VerticalAlignment = VerticalAlignment.Center,
                     FontSize = 36
                 }
             }; 
             Dispatcher.Run(); 
         });
         var thread3 = new Thread(() => { 
             bot = new BottomWindow() {
                 Content = new TextBlock() {
                     Text = "Bottom Window",
                     HorizontalAlignment = HorizontalAlignment.Center,
                     VerticalAlignment = VerticalAlignment.Center,
                     FontSize = 36
                 }
             }; 
             Dispatcher.Run(); 
         });
         thread1.SetApartmentState(ApartmentState.STA);
         thread2.SetApartmentState(ApartmentState.STA);
         thread3.SetApartmentState(ApartmentState.STA);
         thread1.Start();
         thread2.Start();
         thread3.Start();
         App.Current.MainWindow = new RootWindow() {
             TopWin = top,
             MidWin = mid,
             BotWin = bot
         };
         App.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
         App.Current.MainWindow.Show();
     }
 }

You could download the project from GitHub. How do you solve these issues?

windows-wpf
test.gif (1.5 MiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

EmonHaque-1485 avatar image
0 Votes"
EmonHaque-1485 answered EmonHaque-1485 commented

No clue, from where did I get the idea of using ArrangeOcerride in subclass of Window. It works fine without that:

107312-test.gif

Still couldn't figure out why is there the gap between Middle and Bottom window! Tried SnapsToDevicePixels = true in several places BUT that didn't solve the issue! Other than that, looks like everything works as expected. Updated the project in GitHub.


test.gif (557.1 KiB)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@EmonHaque-1485
I tested your project and reproduced your error, it seems Rectangle rendering issue. I update your code as:

     midRect = new Rectangle() { Margin = new Thickness(0, 0, 0, -1) };
     botRect = new Rectangle() { Margin = new Thickness(0, -1, 0, 0), };

This can avoid your issue, maybe you could use it as a workaround.

1 Vote 1 ·

@DaisyTian-MSFT, no, I've removed those Rectangles and set Top of them manually in resizeOthers method of RootWindow.cs, it still produces the gap!

0 Votes 0 ·