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:
- 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>
> 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