Maxim_A
Maxim_A

Reputation: 377

WPF Visualbrush does not update if the page is not displayed in the frame

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.

Upvotes: 0

Views: 394

Answers (1)

Arie
Arie

Reputation: 5373

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);
    }

Upvotes: 1

Related Questions