0

Friends! There is a main window, it contains a frame with which I switch between pages. I have a page that has a canvas. Canvas in the background stream receives data in the form of images in a mosaic view.

foreach (var item in CoreData.fillWallArray.GetConsumingEnumerable())
{
    if (File.Exists(item.PathFile))
    {
          Application.Current.Dispatcher.Invoke(new Action(() =>
          {
               Image image = new Image();
               image.Source = BitmapImageFromFile(item.PathFile);
               image.Width = (int)Math.Truncate((CoreData.settings.CellWidth * 30) / 2.54);
               image.Height = (int)Math.Truncate((CoreData.settings.CellHeight * 30) / 2.54);
               Canvas.SetLeft(image, item.Column * (int)Math.Truncate((CoreData.settings.CellWidth * 30) / 2.54));
               Canvas.SetTop(image, item.Row * (int)Math.Truncate((CoreData.settings.CellHeight * 30) / 2.54));
               can.Children.Add(image);
          }));
          Thread.Sleep(100);
    }
}

My task is to bring this canvas to the second screen. To do this, I create a second window and, as a context, pass the canvas that I need.

var _BroadcastWindow = new BroadcastWindow();
_BroadcastWindow.DataContext = this.can;
_BroadcastWindow.Show();

And in the second window, I link the data.

<Grid>
    <Grid.Background>
        <VisualBrush Visual="{Binding}"/>
    </Grid.Background>
</Grid>

Everything works fine, data from the canvas synchronously displayed in the second window. But as soon as I switch to another page, the Visualbrush is no longer updated. As soon as I switch back to the page with the canvas I see in the second window, it is updated. What could be the problem? I also tried to call Measure, Arrange, UpdateLayout when adding data to the canvas in the background thread, but this did not produce results.

Maxim_A
  • 183
  • 2
  • 13
  • Do you mean that it's not updated when it's invisible? – mm8 Jun 28 '19 at 08:38
  • Yes that's right. VisualBrush is not updated if I go to another page in the frame. As soon as I return to the frame with the canvas in the frame, the update continues. – Maxim_A Jun 28 '19 at 08:45
  • Why do you need to update it if it's invisible...? – mm8 Jun 28 '19 at 08:47

1 Answers1

1

I assume when you say "go to another page" you mean something along the lines of:

frame.Navigate(new System.Uri("Page2.xaml", UriKind.RelativeOrAbsolute));

Every time you do this, your app loads a new Page from a given source. If the current page happens to be the Page that has your Canvas on it, navigation will create a new Canvas instance. If not, and there is no JournalEntry.KeepAlive="true" set for the Page with your Canvas, then contents of the Frame will just get recreated from the Source file every time it is displayed, and a new Canvas will be created with it. Something will get disconnected or prematurely destroyed at some point. Even with KeepAlive set to True, you'll probably just end up with multiple instances of Canvas loaded in memory. Which one do you want to bind to...?

Some alternative approaches off the top of my head:

  1. Cache the Image itself in your View Model and bind both your Canvas on the Page and the VisualBrush to that.

  2. Cache the whole Canvas in your View Model, then switch its contents as needed.

The second approach required only minimal changes to your code, so I could throw in a working example (although I don't know if it's the most optimal):

In Page1.xaml (the page that displays the Canvas):

<Grid>
    <ContentControl Content="{Binding canvas, Source={x:Static local:CanvasViewModel.Instance}}" />
</Grid>

In BroadcastWindow.xaml:

<Grid>
    <Grid.Background>
        <VisualBrush Visual="{Binding}"/>
    </Grid.Background>
</Grid>

Example singleton View Model to hold the canvas:

public class CanvasViewModel
{   
    Rectangle r = new Rectangle
    {
        Fill = Brushes.Orange,
        Width = 200,
        Height = 100
    };

    Ellipse e = new Ellipse
    {
        Fill = Brushes.DodgerBlue,
        Width = 100,
        Height = 100
    };


    public Canvas canvas { get; set; }

    public void Initialize()
    {
        canvas = new Canvas();
        Switch(1);
    }

    // Here the contents of the canvas are switched
    // I called it from Click events of two Buttons outside of Frame
    // In your case, I imagine it will be something like this:
    // public void LoadImage(string path) {...}
    public void Switch(int imageNr)
    {
        switch (imageNr)
        {
            case 1:
                    canvas.Children.Clear();
                    canvas.Children.Add(r);
                break;
            case 2:
                {
                    canvas.Children.Clear();
                    canvas.Children.Add(e);
                }
                break;
            default:
                break;
        }
    }

    #region CONSTRUCTOR

    static CanvasViewModel() { }
    private CanvasViewModel() { }

    private static CanvasViewModel GetAppViewModelHolder()
    {
        CanvasViewModel vh = new CanvasViewModel();
        vh.Initialize();
        return vh;
    }

    #endregion

    #region SINGLETON Instance

    private static readonly object _syncRoot = new object();
    private static volatile CanvasViewModel instance;

    public static CanvasViewModel Instance
    {
        get
        {
            var result = instance;
            if (result == null)
            {
                lock (_syncRoot)
                {
                    if (instance == null)
                    {
                        result = instance = GetAppViewModelHolder();
                    }
                }
            }
            return result;
        }
    }

    #endregion
}

Switching between images in the Click event of a Button outside of Frame:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        CanvasViewModel.Instance.Switch(2);
    }
Arie
  • 5,251
  • 2
  • 33
  • 54
  • switching between pages is as follows: GridMain.NavigationService.Navigate (_monitorStatus); where _monitorStatus is an instance of the page, which is initialized to MainWindow. this way I don’t load a new page every time and KeepAlive is not needed in this case. – Maxim_A Jul 01 '19 at 08:30
  • I also tried the implementation using singleton. but the problem is that I need the canvas display to be the same in two places. and if to use singleton, then display of a canvas is carried out only in one place. – Maxim_A Jul 01 '19 at 08:30
  • @Maxim_A the above Singleton implementation does exactly the same thing you do in your code: you basically bind to the same canvas in two different places. What do you mean by "display of a canvas is carried out only in one place"? – Arie Jul 01 '19 at 09:19
  • meaning that in the background stream, the data arrives on the canvas, and they are displayed only in the second window. At the same time, the page with the canvas is just blank. – Maxim_A Jul 01 '19 at 10:03