How to Achieve Lazy Binding of Tab Page Controls in WPF? - wpf

I have an entity class. This entity has lots of properties and entity's data is shown to the user in several TabItems of a TabControl. I also implement MVVM approach.
When the screen is shown to the user first, I want to bind only the active tab page controls and as the user navigates through tab pages additional separate bindings will be incurred as-needed. How can I achieve that?

You don't have anything to do, that's the default behavior. The DataTemplate for a TabItem content won't be instantiated until this TabItem is selected
EDIT: here's an example:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:Page1ViewModel}">
<v:Page1View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Page3ViewModel}">
<v:Page3View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:Page3ViewModel}">
<v:Page3View />
</DataTemplate>
</Window.Resources>
...
<TabControl ItemsSource="{Binding Pages}"
DisplayMemberPath="Title">
</TabControl>
In the code above, the TabControl will pick the appropriate DataTemplate based on the item type, and will render it only when that item is selected.
EDIT 2: apparently you want to display the data of a single ViewModel on several pages. If you want the controls of each TabItem to lazily instantiated, you need to use the ContentTemplate property of each TabItem:
<TabControl>
<TabItem Header="Page 1">
<TabItem.ContentTemplate>
<DataTemplate>
<v:Page1View />
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>
<TabItem Header="Page 2">
<TabItem.ContentTemplate>
<DataTemplate>
<v:Page2View />
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>
<TabItem Header="Page 3">
<TabItem.ContentTemplate>
<DataTemplate>
<v:Page3View />
</DataTemplate>
</TabItem.ContentTemplate>
</TabItem>
</TabControl>

I created this solution which works for our third party tabcontrol.
The idea is to "intercept" the datacontext when it is beeing set, save it for later and set the datacontext back to null. When the tabitem gets focus we then set the datacontext and the data will populate in the tab.
Implemented as a dependency property. Then simply set the property on the tabs that need to (do not set it on the tab that comes up as a default)
#region SavedDataContext
private static object GetSavedDataContext(TabItemEx tabItem)
{
return tabItem.GetValue(SavedDataContextProperty);
}
private static void SetSavedDataContext(TabItemEx tabItem, object value)
{
tabItem.SetValue(SavedDataContextProperty, value);
}
public static readonly DependencyProperty SavedDataContextProperty =
DependencyProperty.RegisterAttached("SavedDataContext", typeof(object),
typeof(Attach), new UIPropertyMetadata(null));
#endregion
#region LazyLoad
public static bool GetLazyLoad(TabItemEx tabItem)
{
return (bool)tabItem.GetValue(LazyLoadProperty);
}
public static void SetLazyLoad(TabItemEx tabItem, bool value)
{
tabItem.SetValue(LazyLoadProperty, value);
}
private static readonly DependencyProperty LazyLoadProperty =
DependencyProperty.RegisterAttached("LazyLoad", typeof(bool),
typeof(Attach), new UIPropertyMetadata(false, LazyLoadPropertyChanged));
private static void LazyLoadPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs eventArgs)
{
if ((bool)eventArgs.NewValue)
{
var tabItemEx = sender as TabItemEx;
if (tabItemEx == null)
return;
tabItemEx.DataContextChanged += DataContextChanged;
tabItemEx.GotFocus += TabGotFocus;
}
}
#endregion
private static void TabGotFocus(object sender, RoutedEventArgs e)
{
var tabItemEx = sender as TabItemEx;
if (tabItemEx == null)
return;
tabItemEx.GotFocus -= TabGotFocus;
tabItemEx.DataContext = GetSavedDataContext(tabItemEx);
tabItemEx.IsSelected = true;
}
private static void DataContextChanged(object sender, DependencyPropertyChangedEventArgs eventArgs)
{
var tabItemEx = sender as TabItemEx;
if (tabItemEx == null)
return;
SetSavedDataContext(tabItemEx, eventArgs.NewValue);
tabItemEx.DataContextChanged -= DataContextChanged;
tabItemEx.DataContext = null;
}

Marked as answer approach has one drawback - content of TabItem will be always re-rendered when selected. If it's critical - you can try this.

Related

Property bound to DependencyProperty won't update despite TwoWay Binding set

I have a little problem here. I've created custom TreeView using RadTreeView. It all works nice, but I've encountered an obstacle. I've set DependencyProperty for SelectedItem in TreeView. I nest my control in View, bind property to SelectedItem in TwoWay mode, but bound property won't update, it's null all the time, despite DependencyProperty value being set.
Here's tree xaml:
<Grid xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:sdk='http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk'
xmlns:telerik='http://schemas.telerik.com/2008/xaml/presentation' x:Name='this' >
<Grid.Resources>
<DataTemplate x:Key='ChildTemplate'>
<TextBlock Text='{Binding Path=ChildPath}' Margin='5,0' />
</DataTemplate>
<telerik:HierarchicalDataTemplate x:Key='NameTemplate' ItemsSource='{Binding ChildrenCollectionPath}' ItemTemplate='{StaticResource ChildTemplate}'>
<TextBlock Text='{Binding Path=ParentPath }' Padding='7'/>
</telerik:HierarchicalDataTemplate>
</Grid.Resources>
<telerik:RadTreeView x:Name='rtvTreeView' Padding='5' BorderThickness='0' IsEditable='False' IsLineEnabled='True' IsExpandOnDblClickEnabled='False' ItemTemplate='{StaticResource NameTemplate}' />
</Grid>
Below is way I nest the control in View:
<windows:TreeViewReuse CollectionSource="{Binding SitesCollectionWithAddress}" ParentPath="Napis" Grid.Column="0" BorderThickness="2" SelectedItemD="{Binding SelectedSide, ElementName=this, UpdateSourceTrigger=Explicit, Mode=TwoWay}" ChildPath="FullAddress" ChildrenCollectionPath="AdresyStrony" BorderBrush="Red" DoubleClickCommand="{Binding TreeViewDoubleClick}">
</windows:TreeViewReuse>
And here's Tree's code behind in parts:
public partial class TreeViewReuse : UserControl
{
static Telerik.Windows.FrameworkPropertyMetadata propertyMetaData = new Telerik.Windows.FrameworkPropertyMetadata(null,
Telerik.Windows.FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(SelectedItemChangedCallback));
public object SelectedItemD
{
get { return GetValue(SelectedItemDProperty); }
set { SetValue(SelectedItemDProperty, value); }
}
public static readonly DependencyProperty SelectedItemDProperty =
DependencyProperty.Register("SelectedItemD", typeof(object), typeof(TreeViewReuse), propertyMetaData);
public TreeViewReuse()
{
InitializeComponent();
Loaded += new RoutedEventHandler(TreeViewReuse_Loaded);
}
void treeView_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangedEventArgs e)
{
SelectedItemD = _treeView.SelectedItem;
}
static private void SelectedItemChangedCallback(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
}
Does anyone have an idea why property bound to SelectedItemD does not update? I don't care about setting tree's selected item from it, I only want to set it to selected item.
Here's property:
public StronaSprawy SelectedSide
{
get
{
return _selectedSide;
}
set
{
_selectedSide = value;
}
}
Your Dependency Property looks fine.. all except for that Telerik.Windows.FrameworkPropertyMetadata instance.
Silverlight does not support setting meta data options, so I cant think how the Telerik implementation will achieve that. It is possible that Telerik have their own DP implementation, or even that this type of property meta data only works with their controls.
Try using the standard System.Windows.PropertyMetaData type instead and see if that works for you.

How to display a image on mouse over in silverlight data grid

I need to display a image on mouse over only in silverlight 5.
Can any one please help me.
Give me any idea how to achieve it...
<sdk:DataGridTemplateColumn x:Name="colDeleteContent" IsReadOnly="True" Header="Delete Content" Width="100" CanUserResize="False">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel x:Name="spDeleteContent" VerticalAlignment="Center" Margin="10,0,0,0" Width="20" Height="20" HorizontalAlignment="Center" Orientation="Vertical">
<Image x:Name="imgDeleteContent" Source="Assets/Images/close.png" Height="15" Width="15" Margin="0" MouseLeftButtonDown="imgDeleteContent_MouseLeftButtonDown" Cursor="Hand"/>
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
Neon
There are many ways, OnFocus set your images Visibility Visible and on FocusLeft set Collapsed basically of your main element.
But I see that it is on DataTemplate on your sample.
So There are some ways I imagine.
1)Create a new component instead of element in DataTemplate such as
namespace ProjectBus
{
public class StackPanelHasHiddenImage : Control
{
//You may don't need dependency property
//It supports bindability
#region dependency property
public static Image GetMyProperty(DependencyObject obj)
{
return (Image)obj.GetValue(ImageProperty);
}
public static void SetMyProperty(DependencyObject obj, Image value)
{
obj.SetValue(ImageProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached("Image", typeof(Image), typeof(StackPanelHasHiddenImage), new System.Windows.PropertyMetadata(null));
#endregion
public Image Image
{
get;
set;
}
protected override void OnGotFocus(RoutedEventArgs e)
{
Image.Visibility = Visibility.Visible;
base.OnGotFocus(e);
}
protected override void OnLostFocus(RoutedEventArgs e)
{
Image.Visibility = Visibility.Collapsed;
base.OnLostFocus(e);
}
}
}
Then in your xaml use like
<DataTemplate>
<local:StackPanelHasHiddenImage Image="/ProjectBus;component/blabal.png"/>
</DataTemplate>
2) Use GotoStateAction behaviour
http://msdn.microsoft.com/en-us/library/ff723953%28v=expression.40%29.aspx but I see that its in a DataTemplate and using this may not be easier.
3) MainElement.FinChildByType < StackPanel >().FirstOrDefault() is not null then add your focus and unfocus handler to this element on your codebehind. But this is a method I mostly avoid to use.
Its a bit harder because its in a template so your named object in template can't be seen on your codebehind.
Hope helps

How to bind local property on control in WPF

I have two controls on WPF
<Button HorizontalAlignment="Center"
Name="btnChange"
Click="btnChange_Click"
Content="Click Me" />
<Label Name="lblCompanyId"
HorizontalAlignment="Center"
DataContext="{Binding ElementName=_this}"
Content="{Binding Path=CompanyName}" />
As we can see that label is bound to local property(in code Behind), I don't see any value on label when I click button...
Below is my code behind...
public static readonly DependencyProperty CompanyNameProperty =
DependencyProperty.Register("CompanyName", typeof(string), typeof(Window3), new UIPropertyMetadata(string.Empty));
public string CompanyName {
get { return (string)this.GetValue(CompanyNameProperty); }
set { this.SetValue(CompanyNameProperty, value); }
}
private void btnChange_Click(object sender, RoutedEventArgs e) {
this.CompanyName = "This is new company from code beind";
}
Try:
Content="{Binding ElementName=_this, Path=CompanyName}"
Without the DataContext binding.
EDIT
I have no problems with your code, have named your window to x:Name="_this"?
<Window x:Class="WpfStackOverflowSpielWiese.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window3"
Height="300"
Width="300"
x:Name="_this">
<Grid>
<StackPanel>
<Button HorizontalAlignment="Center"
Name="btnChange"
Click="btnChange_Click"
Content="Click Me" />
<Label Name="lblCompanyId"
HorizontalAlignment="Center"
DataContext="{Binding ElementName=_this}"
Content="{Binding Path=CompanyName}"></Label>
</StackPanel>
</Grid>
</Window>
Is your window really Window3?
public partial class Window3 : Window
{
public Window3() {
this.InitializeComponent();
}
public static readonly DependencyProperty CompanyNameProperty =
DependencyProperty.Register("CompanyName", typeof(string), typeof(Window3), new UIPropertyMetadata(string.Empty));
public string CompanyName {
get { return (string)this.GetValue(CompanyNameProperty); }
set { this.SetValue(CompanyNameProperty, value); }
}
private void btnChange_Click(object sender, RoutedEventArgs e) {
this.CompanyName = "This is new company from code behind";
}
}
You are currently binding your Label's DataContext to a Button, and then trying to set it's Content to CompanyName, however CompanyName is not a valid property on Button
Specify DataContext in your binding Path to bind to Button.DataContext.CompanyName instead of Button.CompanyName
Also, I'd recommend just binding the Content instead of binding both the DataContext and Content
<Label Content="{Binding ElementName=btnChange, Path=DataContext.CompanyName}" />
And if your code looks exactly like the code sample posted, then both the Button and Label have the same DataContext, so you can bind directly to CompanyName
<Label Content="{Binding CompanyName}" />
Edit
Just noticed that your Label's binding was to a control named _this. I had assumed it was the Button, although I see now that your Button's name is btnChange, not _this.
It doesn't matter though, the answer is still the same. You're trying to bind to a UI Control's CompanyName property, which is not a valid property.

Textbox in listview - update source and move focus on tab not working at same time

I have a listview. I have set following int that :-
<ListView KeyboardNavigation.TabNavigation="Local" SelectionMode="Extended">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
</Style>
</ListView.ItemContainerStyle>
One column in listview contains TextBox's.
If I set the UpdateSourceTrigger=LostFocus
in my textbox, I can not tab through the listview...Instead if I set UpdateSourceTrigger=Explicit, the tabbing is working...but source will not get updated.
Please help me
EDIT
public class TextBoxBehavior
{
#region Attached Property EscapeClearsText
public static readonly DependencyProperty EscapeClearsTextProperty
= DependencyProperty.RegisterAttached("EscapeClearsText", typeof(bool), typeof(TextBoxBehavior),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnEscapeClearsTextChanged)));
private static void OnEscapeClearsTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
var textBox = d as TextBox;
if (textBox != null)
{
textBox.KeyUp -= TextBoxKeyUp;
textBox.KeyUp += TextBoxKeyUp;
}
}
}
private static void TextBoxKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
//((DataContext<string>)((TextBox)sender).GetBindingExpression(TextBox.TextProperty).DataItem).RollbackChanges();
((TextBox)sender).Text = string.Empty;
}
else if (e.Key == Key.Enter)
{
((TextBox)sender).GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
public static void SetEscapeClearsText(DependencyObject dependencyObject, bool escapeClearsText)
{
if (!ReferenceEquals(null, dependencyObject))
dependencyObject.SetValue(EscapeClearsTextProperty, escapeClearsText);
}
public static bool GetEscapeClearsText(DependencyObject dependencyObject)
{
if (!ReferenceEquals(null, dependencyObject))
return (bool)dependencyObject.GetValue(EscapeClearsTextProperty);
return false;
}
#endregion Attached Property EscapeClearsText
}
Below is the listview/gridview column which has the attached property in it.
<GridViewColumn Width="60">
<GridViewColumnHeader Content="Priority"
Command="{Binding Path=SortSelectedClaimCodeGroupsCommand}"
CommandParameter="Item.IntPriority">
</GridViewColumnHeader>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border DataContext="{Binding Item.Priority}"
Style="{StaticResource ValidationResultBorderStyle}" HorizontalAlignment="Left" >
<TextBox Width="200" MaxLength="25" Text="{Binding Path=Value,Mode=TwoWay,
UpdateSourceTrigger=Explicit}" local:TextBoxBehavior.EscapeClearsText="True" >
When you set the UpdateSourceTrigger as explicit, you have to update the source by explicitly calling the method UpdateSource on your BindingExpression. Where is the code for that?
EDIT
In your TextBoxKeyUp event you are overwriting your Binding by setting the text on the press of Escape key. Firstly you bind it to the property Value and later you are explicitly setting the Textbox text property to String.Empty.This way text property will loose it's binding. So, later whenever you call UpdateSource it won't propagate to Source value since it's no longer binded to the Text property of textbox. Instead you should set the text like this -
((TextBox)sender).SetCurrentValue(TextBox.TextProperty, String.Empty);
This way your binding will be preserved and UpdateSource would work as it should.

How to make the URL in textblock be clickable?

The content of the textblock is imported from a web service, but somehow there is a URL.
Is it possible to make it a link?
Thanks.
Sounds like you want a LinkLabel control. I've used that control with some modifications in my Silverlight Twitter Badge to mix the text and links that show up in tweets.
If you just have a TextBlock with a link only and want that clickable then you just set the cursor to be a hand and add an event handler for the MouseLeftButtonDown event that would navigate to the value of the TextBox.
Xaml:
<TextBlock Text="http://www.microsoft.com" Cursor="Hand" TextDecorations="Underline" MouseLeftButtonDown="TextBlock_MouseLeftButtonDown" />
Code:
private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var txt = ((TextBlock)sender).Text;
System.Windows.Browser.HtmlPage.Window.Navigate(new Uri(txt, UriKind.Absolute));
}
You could do something like the following; however this makes use of a Label and not a textblock.
In your XAML you do the following:
<dataInput:Label Grid.Row="2">
<ContentPresenter>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Hello world"/>
<HyperlinkButton x:Name="Test" NavigateUri="{Binding Path=URI}" Content="This is a url"/>
</StackPanel>
</ContentPresenter>
</dataInput:Label>
and in your code behind you add the following dependency property and set the datacontext to the page itself
public static readonly DependencyProperty URLProperty =
DependencyProperty.Register("URI", typeof(Uri), typeof(MainPage), null);
public Uri URI { get
{
return (Uri)GetValue(URLProperty);
}
set
{ SetValue(URLProperty, value); }
}
This code sets the dependency property for the binding to the URL;
public MainPage()
{
InitializeComponent();
URI = new Uri("/Home", UriKind.Relative);
DataContext = this;
}
This code creates a new URI and binds it to the variable. It also sets the data context to the page itself.

Resources