Reputation: 4488
I have a window that is databound to a ViewModel. The ViewModel has many properties, some of which are expensive to evaluate and are not always needed by the user.
What I would like to do is have these properties databound to controls contained in different unexpanded expanders. When the expander is expanded by the user I would like the controls to bind and the properties to be evaluated. If the user does not expand the expander, the properties should not be evaluated, and the cost not incurred.
I could certainly do this using events from the expander but I'm trying to find a nice MVVM way of doing it. Any suggestions?
Upvotes: 3
Views: 1887
Reputation: 6316
<Expander Expanded="Expander_Expanded">
<....
</Expander>
in .cs:
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
var expander = (Expander)sender;
if(expander.DataContext == null)
expander.DataContext = ViewModel;
}
Upvotes: 1
Reputation: 27250
Would something like this work?
<Expander Header="Diagnostics" IsExpanded="False">
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="DataContext" Value="{x:Null}" />
<Style.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.Diagnostics}" />
</Trigger>
</Style.Triggers>
</Style>
</Expander.Style>
<TextBlock Text="{Binding TestValue}" />
</Expander>
If you just want the expanded binding to be the datacontext of the parent window, then presumably this would work in the trigger:
<Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext}" />
Upvotes: 1
Reputation: 43021
@Alain's suggestion looks interesting.
In addition, since your properties are expensive to evaluate, you should also look at async bindings and priority bindings.
An async example:
<TextBox Text={Binding Path=Description, IsAsync="True"}/>
Setting IsAsync=True will prevent binding evaluation from blocking the UI thread.
You can also use priority binding, which is a form of multi-binding:
<Grid>
<Grid.DataContext>
<Samples:PriorityBindingViewModel/>
</Grid.DataContext>
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding Path="ExpensiveProperty" Mode="OneWay" IsAsync="True"/>
<Binding Path="LessExpensiveProperty" Mode="OneWay" IsAsync="True"/>
<Binding Path="LeastExpensiveProperty" Mode="OneWay" IsAsync="True"/>
</PriorityBinding>
</TextBox.Text>
</TextBox>
</Grid>
Bindings are evaluated with priority from top to bottom. This allows you to put say a 'loading ...' message in the lowest priority binding (LeastExpensiveProperty) while the high priority but slower bindings are being evaluated.
public class PriorityBindingViewModel
{
private string _expensiveProperty;
private string _lessExpensiveProperty;
public PriorityBindingViewModel()
{
ExpensiveProperty = "Loaded";
LessExpensiveProperty = "Still loading";
LeastExpensiveProperty = "Loading";
}
public string ExpensiveProperty
{
get
{
// Thread.Sleep is in place of an 'expensive' operation.
// Just in case anyone doesn't realize.
Thread.Sleep(8000);
return _expensiveProperty;
}
private set { _expensiveProperty = value; }
}
public string LessExpensiveProperty
{
get
{
// Same here.
Thread.Sleep(3000);
return _lessExpensiveProperty;
}
private set { _lessExpensiveProperty = value; }
}
public string LeastExpensiveProperty { get; private set; }
}
Upvotes: 3