-2

In regarding with this SO question here, it is posible to update other tab content from code behind and let caching allow to re-cache changed UI elements? Like in scenario I have updated DataGrid scroll index for some tabs on TabControl on some event,

dgvLogs.ScrollIntoView( log );

Now since tab is already cached and above change not reflecting when user switch to tab where dgvLogs located.

EDIT

I have tab control (ExTabControl) in main window and multiple tab holding datagrid which displaying some application logs inside it. Like this:

ExTabControl like this:

<controls:ExTabControl Grid.Row="1" ItemsSource="{Binding Tabs, Mode=OneWay}" >
            <controls:ExTabControl.Resources>
                <Style TargetType="{x:Type TabItem}">

                </Style>
            </controls:ExTabControl.Resources>
</controls:ExTabControl>

Single tab having datagrid like this:

<DataGrid Name="dgvLogs" ItemsSource="{Binding Logs}" VerticalScrollBarVisibility="Auto" FrozenColumnCount="4">

Problem:

Lets say I have 3 tab in ExTabControl, selected tab is 1 and from code behind have have update scroll index for tab 2 using dgvLogs.ScrollIntoView( someInbetweenlog );. Ideally if I do select tab 2 then select scroll index inside dgvLogs should be where someInbetweenlog is located. But unfortunately tab 2 scroll not moving as per changes made code behind..

If I do make use of default tab control i.e. TabControl insted of ExTabControl then it is working fine as expected. but if I move scroll in any of tab for dgvLogs then it is reflecting in other tabs also..

Please add comment I'll post more code if required.

EDIT 2

I have created sample application in which I tried to demonstrate the issue. In this app I have added context menu for grid in tab and using Sync option I am trying to scroll to view where first matching log found with closed selected log, in other opened tabs.

Issue: ExTabControl unable to scroll to required log item in different opened tab.

https://github.com/ankushmadankar/StackOverflow54198246/

Ankush Madankar
  • 3,689
  • 4
  • 40
  • 74
  • 2
    Please provide a [mcve]. – dymanoid Jan 21 '19 at 12:15
  • If you only have 3 tabs then you could consider putting the tabitems directly in the tabcontrol rather than binding itemssource. That way the controls will stay in memory and retain state. This might, of course, have some downsides. – Andy Jan 25 '19 at 15:53
  • @dymanoid I would happy if you ask specific question regarding unclear part. I know guidelines to ask question on SO. – Ankush Madankar Jan 29 '19 at 06:07
  • @Andy Right now tabs added dynamically inside `ExTabControl`. – Ankush Madankar Jan 29 '19 at 06:18
  • If I didn't already know the tabitems were added dynamically then it would have been a bit odd to suggest putting the tabitems directly in the tabcontrol. Your other option is to defer bring into view until the user navigates to the tabitem. Maybe add a List> and when they navigate look at what you have in the indexed list and execute any actions. You can then add an action to that list in the appropriate index to bringintoview or whatever. – Andy Jan 29 '19 at 10:23
  • @Andy I have created sample application to demonstrate the issue, please check the update question. – Ankush Madankar Feb 01 '19 at 12:51

2 Answers2

2

If I do make use of default tab control i.e. TabControl instead of ExTabControl then it is working fine as expected. But if I move scroll in any of tab for dgvLogs then it is reflecting in other tabs also.

There are two uses of TabControl, extending on this post:

  1. When we bind ItemsSource to a list of items, and we have set the same DataTemplate for each item, TabControl will create only one "Content" view for all items. And when a different tab item is selected, the View doesn't change but the backing DataContext is bound to the viewmodel of the newly selected item.

Is it possible to update other tab content from code behind and let caching allow to re-cache changed UI elements?

The reason the updates won't work is because of another WPF optimization, from UIElement.IsVisible:

Elements where IsVisible is false do not participate in input events (or commands), do not influence either the measure or arrange passes of layout, are not focusable, are not in a tab sequence, and will not be reported in hit testing.

You can change properties on cached elements, but some operations require that an UIElement is visible in order to take effect.

Worth noting:

  • If you call ScrollIntoView on a DataGrid that's not visible, it won't scroll to the given object. So the ScrollToSelectedBehavior from your linked project is intended to scroll a datagrid that is visible during the process.
  • In the code of ExTabControl the method UpdateSelectedItem sets the visibility of non active contentpresenters to collapsed.

Given you've explicitly asked for code behind,

A quick hack

TraceViewerView.xaml

<DataGrid IsVisibleChanged="dgvLogs_IsVisibleChanged" ... >

TraceViewerView.xaml.cs

private void dgvLogs_IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
    if (sender is DataGrid dataGrid && dataGrid.IsVisible)
    {
        TraceViewerViewModel viewModel = (TraceViewerViewModel)DataContext;
        if (viewModel.Log != null)
            dataGrid.ScrollIntoView(viewModel.Log);
    }
}

A couple of remarks:

  • You can now remove the line local:ScrollToSelectedBehavior.SelectedValue="{Binding Log}" as we are fetching the sync value straight from the viewmodel.
  • This is a hack, the view is hard coded to your viewmodel, which is likely to blow up at some time.

A better way

First, to keep our code loosely coupled, an interface.

interface ISync
{
    object SyncValue { get; }
}

TraceViewerModel.cs

public class TraceViewerViewModel : PropertyObservable, ITabItem, ISync

Rename Log to SyncValue, and replace the original code

private TraceLog synclog;
public TraceLog Log
{
    get { return synclog; }
    private set
    {
        synclog = value;

        OnPropertyChanged();
    }
}

with

public object SyncValue { get; set; }

Basically, we're trading in a Binding for an interface. The reason I went for the interface in this specific use case, is that you only need to check a tab's sync value when you move to it (making a full fledged Binding a bit overkill).

Next, let's create a Behavior that does what you want.

Instead of an Attached Property I'll use Interactivity Behaviors, which provide a more encapsulated way to extend functionality (requires System.Windows.Interactivity).

ScrollToSyncValueBehavior.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace WpfApp1
{
    public class ScrollToSyncValueBehavior : Behavior<DataGrid>
    {
        protected override void OnAttached()
        {
            this.AssociatedObject.IsVisibleChanged += OnVisibleChanged;
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.IsVisibleChanged -= OnVisibleChanged;
        }

        private static void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (sender is DataGrid dataGrid && dataGrid.IsVisible)
            {
                ISync viewModel = dataGrid.DataContext as ISync;
                if (viewModel?.SyncValue != null)
                    dataGrid.ScrollIntoView(viewModel.SyncValue);
            }
        }
    }
}

TraceViewerView.xaml

<UserControl x:Class="WpfApp1.TraceViewerView"

             ... 

             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <DataGrid CanUserAddRows="false" GridLinesVisibility="None" AutoGenerateColumns="False" 
                  ItemsSource="{Binding Logs}">
            <i:Interaction.Behaviors>
                <local:ScrollToSyncValueBehavior />
            </i:Interaction.Behaviors>
            
            ...

        </DataGrid>      
    </Grid>
</UserControl>
Community
  • 1
  • 1
Funk
  • 10,976
  • 1
  • 17
  • 33
1

You need to derive TabControl as described in this answer. Using that technique, the visual tree for each tab will be preserved.

Please note that if you have lots of tabs the caching will imply a significant performance impact. I would recommend using it for at most 10 tabs.

l33t
  • 18,692
  • 16
  • 103
  • 180
  • Thank you for your response. I know how to use `ExTabControl` but control not working in case we update visual tree code behind for single tree. It do pick same cached content. – Ankush Madankar Jan 29 '19 at 06:10
  • I have created sample application to demonstrate the issue, please check the update question. – Ankush Madankar Feb 01 '19 at 12:51