Implement DataTemplate DependencyProperty in UserControl - silverlight

I'm trying to make an intertia touch scrolling list in a UserControl in Silverlight 4 using Expression Blend 4. I've already made the dependency properties in my UserControl which i want to work like the ListBox does. ItemSource is the list of objects i want to show in my list and datatemplate is the way it should be shown.
How do i deal with these properties inside my UserControl? I have a StackPanel where all the datatemplates should be added showing the data ofc.
How do i apply the data in my IEnumerable to the DataTemplate when looping through the ItemSource to add them to the list (StackPanel).
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(InertiaScrollBox), null);
public IEnumerable ItemsSource
{
get{ return (IEnumerable)GetValue(ItemsSourceProperty); }
set{ SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(InertiaScrollBox), null);
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
This was kinda hard to explain but hope you understand otherwise please ask. Thanks in advance

Dependency properties are rather useless if to not handle their changes.
At first you should add PropertyChanged callbacks. In my example I add them inline and call the UpdateItems private method.
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(InertiaScrollBox),
new PropertyMetadata((s, e) => ((InertiaScrollBox)s).UpdateItems()));
public static readonly DependencyProperty ItemTemplateProperty =
DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(InertiaScrollBox),
new PropertyMetadata((s, e) => ((InertiaScrollBox)s).UpdateItems()));
Then you can call the LoadContent method of the DataTemplate class and set an item from the ItemsSource as the DataContext to the returned visual element:
private void UpdateItems()
{
//Actually it is possible to use only the ItemsSource property,
//but I would rather wait until both properties are set
if(this.ItemsSource == null || this.ItemTemplate == null)
return;
foreach (var item in this.ItemsSource)
{
var visualItem = this.ItemTemplate.LoadContent() as FrameworkElement;
if(visualItem != null)
{
visualItem.DataContext = item;
//Add the visualItem object to a list or StackPanel
//...
}
}
}

Related

WPF TabItem state lost when selected tab changes

I have a TabControl with multiple tabs. Each tab has a multiline TextBox in it.
So in the visible textbox in Tab 1, the insertion point starts out at the beginning of line 1.
I move the insertion point to the end of line 2.
I change the selected tab to Tab 2, and then back to Tab 1.
The insertion point in the Tab 1 TextBox is always back at the start of line one.
Both tabs are being populated by viewmodels in a list:
<TabControl ItemsSource="{Binding TabItemViewModelList}">
<TabControl.Resources>
<DataTemplate DataType="TabItemViewModel">
<Grid>
<TextBox Text="{Binding Text}" />
</Grid>
</DataTemplate>
</TabControl.Resources>
<TabControl>
That's simplified, but not by much.
Is this because it's re-instantiating the template each time I change the selected item, or something like that? Is there any way around that other than agonizingly maintaining every tiny bit of UI state in the ViewModel?
Update
I was right, this is the virtualization bug, it's a known bug, and the designer probably considered it a feature. He's insane, of course: A tab control is not a list box. It becomes totally unusable long before you've got enough items in it for virtualization to make any sense at all.
The answer seems to be Ivan Krivyakov's TabContent.IsCached attached property, found in this question: Turning off Virtualization for Tabcontrol without itemsource - WPF. It's not the answer to his question, but it is the answer to mine.
Looks like this in the XAML:
<TabControl
xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors"
ikriv:TabContent.IsCached="True"
ItemsSource="{Binding DocumentList.Documents}"
SelectedItem="{Binding DocumentList.ActiveDocument}"
/>
And here's the complete source code to Krivyakov's solution, just in case CodeProject gets hit by an asteroid:
// TabContent.cs, version 1.2
// The code in this file is Copyright (c) Ivan Krivyakov
// See http://www.ikriv.com/legal.php for more information
//
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
/// <summary>
/// http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization
/// </summary>
namespace IKriv.Windows.Controls.Behaviors
{
/// <summary>
/// Attached properties for persistent tab control
/// </summary>
/// <remarks>By default WPF TabControl bound to an ItemsSource destroys visual state of invisible tabs.
/// Set ikriv:TabContent.IsCached="True" to preserve visual state of each tab.
/// </remarks>
public static class TabContent
{
public static bool GetIsCached(DependencyObject obj)
{
return (bool)obj.GetValue(IsCachedProperty);
}
public static void SetIsCached(DependencyObject obj, bool value)
{
obj.SetValue(IsCachedProperty, value);
}
/// <summary>
/// Controls whether tab content is cached or not
/// </summary>
/// <remarks>When TabContent.IsCached is true, visual state of each tab is preserved (cached), even when the tab is hidden</remarks>
public static readonly DependencyProperty IsCachedProperty =
DependencyProperty.RegisterAttached("IsCached", typeof(bool), typeof(TabContent), new UIPropertyMetadata(false, OnIsCachedChanged));
public static DataTemplate GetTemplate(DependencyObject obj)
{
return (DataTemplate)obj.GetValue(TemplateProperty);
}
public static void SetTemplate(DependencyObject obj, DataTemplate value)
{
obj.SetValue(TemplateProperty, value);
}
/// <summary>
/// Used instead of TabControl.ContentTemplate for cached tabs
/// </summary>
public static readonly DependencyProperty TemplateProperty =
DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(TabContent), new UIPropertyMetadata(null));
public static DataTemplateSelector GetTemplateSelector(DependencyObject obj)
{
return (DataTemplateSelector)obj.GetValue(TemplateSelectorProperty);
}
public static void SetTemplateSelector(DependencyObject obj, DataTemplateSelector value)
{
obj.SetValue(TemplateSelectorProperty, value);
}
/// <summary>
/// Used instead of TabControl.ContentTemplateSelector for cached tabs
/// </summary>
public static readonly DependencyProperty TemplateSelectorProperty =
DependencyProperty.RegisterAttached("TemplateSelector", typeof(DataTemplateSelector), typeof(TabContent), new UIPropertyMetadata(null));
[EditorBrowsable(EditorBrowsableState.Never)]
public static TabControl GetInternalTabControl(DependencyObject obj)
{
return (TabControl)obj.GetValue(InternalTabControlProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalTabControl(DependencyObject obj, TabControl value)
{
obj.SetValue(InternalTabControlProperty, value);
}
// Using a DependencyProperty as the backing store for InternalTabControl. This enables animation, styling, binding, etc...
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DependencyProperty InternalTabControlProperty =
DependencyProperty.RegisterAttached("InternalTabControl", typeof(TabControl), typeof(TabContent), new UIPropertyMetadata(null, OnInternalTabControlChanged));
[EditorBrowsable(EditorBrowsableState.Never)]
public static ContentControl GetInternalCachedContent(DependencyObject obj)
{
return (ContentControl)obj.GetValue(InternalCachedContentProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalCachedContent(DependencyObject obj, ContentControl value)
{
obj.SetValue(InternalCachedContentProperty, value);
}
// Using a DependencyProperty as the backing store for InternalCachedContent. This enables animation, styling, binding, etc...
[EditorBrowsable(EditorBrowsableState.Never)]
public static readonly DependencyProperty InternalCachedContentProperty =
DependencyProperty.RegisterAttached("InternalCachedContent", typeof(ContentControl), typeof(TabContent), new UIPropertyMetadata(null));
[EditorBrowsable(EditorBrowsableState.Never)]
public static object GetInternalContentManager(DependencyObject obj)
{
return (object)obj.GetValue(InternalContentManagerProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInternalContentManager(DependencyObject obj, object value)
{
obj.SetValue(InternalContentManagerProperty, value);
}
// Using a DependencyProperty as the backing store for InternalContentManager. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InternalContentManagerProperty =
DependencyProperty.RegisterAttached("InternalContentManager", typeof(object), typeof(TabContent), new UIPropertyMetadata(null));
private static void OnIsCachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj == null) return;
var tabControl = obj as TabControl;
if (tabControl == null)
{
throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name +
". Only objects of type TabControl can have TabContent.IsCached property.");
}
bool newValue = (bool)args.NewValue;
if (!newValue)
{
if (args.OldValue != null && ((bool)args.OldValue))
{
throw new NotImplementedException("Cannot change TabContent.IsCached from True to False. Turning tab caching off is not implemented");
}
return;
}
EnsureContentTemplateIsNull(tabControl);
tabControl.ContentTemplate = CreateContentTemplate();
EnsureContentTemplateIsNotModified(tabControl);
}
private static DataTemplate CreateContentTemplate()
{
const string xaml =
"<DataTemplate><Border b:TabContent.InternalTabControl=\"{Binding RelativeSource={RelativeSource AncestorType=TabControl}}\" /></DataTemplate>";
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabContent).Namespace, typeof(TabContent).Assembly.FullName);
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("b", "b");
var template = (DataTemplate)XamlReader.Parse(xaml, context);
return template;
}
private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (obj == null) return;
var container = obj as Decorator;
if (container == null)
{
var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name +
". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl.";
throw new InvalidOperationException(message);
}
if (args.NewValue == null) return;
if (!(args.NewValue is TabControl))
{
throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name +", it must be of type TabControl");
}
var tabControl = (TabControl)args.NewValue;
var contentManager = GetContentManager(tabControl, container);
contentManager.UpdateSelectedTab();
}
private static ContentManager GetContentManager(TabControl tabControl, Decorator container)
{
var contentManager = (ContentManager)GetInternalContentManager(tabControl);
if (contentManager != null)
{
/*
* Content manager already exists for the tab control. This means that tab content template is applied
* again, and new instance of the Border control (container) has been created. The old container
* referenced by the content manager is no longer visible and needs to be replaced
*/
contentManager.ReplaceContainer(container);
}
else
{
// create content manager for the first time
contentManager = new ContentManager(tabControl, container);
SetInternalContentManager(tabControl, contentManager);
}
return contentManager;
}
private static void EnsureContentTemplateIsNull(TabControl tabControl)
{
if (tabControl.ContentTemplate != null)
{
throw new InvalidOperationException("TabControl.ContentTemplate value is not null. If TabContent.IsCached is True, use TabContent.Template instead of ContentTemplate");
}
}
private static void EnsureContentTemplateIsNotModified(TabControl tabControl)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(TabControl.ContentTemplateProperty, typeof(TabControl));
descriptor.AddValueChanged(tabControl, (sender, args) =>
{
throw new InvalidOperationException("Cannot assign to TabControl.ContentTemplate when TabContent.IsCached is True. Use TabContent.Template instead");
});
}
public class ContentManager
{
TabControl _tabControl;
Decorator _border;
public ContentManager(TabControl tabControl, Decorator border)
{
_tabControl = tabControl;
_border = border;
_tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
}
public void ReplaceContainer(Decorator newBorder)
{
if (Object.ReferenceEquals(_border, newBorder)) return;
_border.Child = null; // detach any tab content that old border may hold
_border = newBorder;
}
public void UpdateSelectedTab()
{
_border.Child = GetCurrentContent();
}
private ContentControl GetCurrentContent()
{
var item = _tabControl.SelectedItem;
if (item == null) return null;
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
if (tabItem == null) return null;
var cachedContent = TabContent.GetInternalCachedContent(tabItem);
if (cachedContent == null)
{
cachedContent = new ContentControl
{
DataContext = item,
ContentTemplate = TabContent.GetTemplate(_tabControl),
ContentTemplateSelector = TabContent.GetTemplateSelector(_tabControl)
};
cachedContent.SetBinding(ContentControl.ContentProperty, new Binding());
TabContent.SetInternalCachedContent(tabItem, cachedContent);
}
return cachedContent;
}
}
}
}
The carret position gets updated every time the text gets re-initialized in your case. You could put a behavior on the text box to set it to the end on focus... see the following: Set the caret/cursor position to the end of the string value WPF textbox

Exposing property of a control inside a WPF UserControl

I have a usercontrol which has a grid control inside
<UserControl x:Class="MyGrid">
<Telerik:RadGridView EnableRowVirtualization="false">
</Telerik:RadGridView/>
</UserControl>
How can I expose the EnableRowVirtualization property of the control inside the usercontrol using DependencyProperty so that when someone uses the MyGrid usercontrol, the user will just do something like this
<grids:MyGrid EnableRowVirtualization="false"> </grids:MyGrid>
UPDATE: Right now, this is just what I came up
public partial class MyGrid //myGrid userControl
{
public bool EnableRowVirtualization
{
get { return (bool)GetValue(EnableRowVirtualizationProperty); }
set { SetValue(EnableRowVirtualizationProperty, value); }
}
// Using a DependencyProperty as the backing store for EnableRowVirtualization. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnableRowVirtualizationProperty =
DependencyProperty.Register("EnableRowVirtualization", typeof(bool), typeof(MaxGridView), new UIPropertyMetadata(false, OnEnableRowVirtualizationPropertyChanged)
);
private static void OnEnableRowVirtualizationPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var grid = (RadGridView)depObj;
if (grid != null)
{
grid.EnableRowVirtualization = (bool)e.NewValue;
}
}
If you give the Telerik grid a name you can then access it from the code of the dependency property. If you also combine that with an PropertyChanged property metadata when you define the dependency property then you can simply relay the value through to the underlying grid.
This is just off the top of my head, but something like this should do the trick:
public static readonly DependencyProperty EnableRowVirtualizationProperty =
DependencyProperty.Register("EnableRowVirtualization"
, typeof(bool)
, typeof(MyGrid)
, new UIPropertyMetadata(false, OnEnableRowVirtualizationPropertyChanged)
);
private static void OnEnableRowVirtualizationPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var myGrid = depObj as MyGrid;
if (myGrid != null)
{
myGrid.InnerTelerikGrid.EnableRowVirtualization = e.NewValue;
}
}
For more information check out DependencyProperty.RegisterAttached Method (String, Type, Type, PropertyMetadata) and UIPropertyMetadata Constructor (Object, PropertyChangedCallback).

DependencyProperty PropertyChangedCallback problem

Sorry for my English.
I try to write UserControl (SearchTextBox...simmillar Firefox search textbox) that consists from TextBox, Popup and ListBox in a Popup. I need to change ItemsSource of ListBox dynamically in my application. So i use DependencyProperty in UserControl:
//STextBox UserControl Code-Behind
public partial class STextBox : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty;
static STextBox()
{
ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(STextBox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsArrange, new PropertyChangedCallback(OnItemsSourceChanged)));
}
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(STextBox.ItemsSourceProperty);
}
set
{
SetValue(STextBox.ItemsSourceProperty, value);
}
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
STextBox c = (STextBox)d;
c.ItemsSource = (IEnumerable)e.NewValue;
}
I can't use bindings to ItemsSource in my app, because two lists for my ListBox-ItemsSource creates on the fly from records of database. I set ItemsSource in code:
//my app code-behind
switch (SomeIF)
{
case 0:
sTextBox.ItemsSource = list1;
break;
case 1:
sTextBox.ItemsSource = list2;
break;
}
But nothing happened. I know exactly that OnItemsSourceChanged method is fired, but new value never assigned to ItemsSource. What I'am doing wrong?
Can not say that I liked, but this solution work.
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
STextBox c = (STextBox)d;
c.OnItemsSourceChanged(e);
}
//added overload method where I can simply set property to the control
protected virtual void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
{
myListBox.ItemsSource = ItemsSource;
}

Why doesn't this Silverlight 4 DependencyObject's DependencyProperty getting data bound?

I have no idea why data binding is not happening for certain objects in my Silverlight 4 application. Here's approximately what my XAML looks like:
<sdk:DataGrid>
<u:Command.ShortcutKeys>
<u:ShortcutKeyCollection>
<u:ShortcutKey Key="Delete" Command="{Binding Path=MyViewModelProperty}"/>
</u:ShortcutKeyCollection>
</u:Command.ShortcutKeys>
</sdk:DataGrid>
The data context is set just fine since other data bindings that I have set on the grid are working just fine. The Command.ShortcutKeys is an attached DependencyProperty that is declared as follows:
public static readonly DependencyProperty ShortcutKeysProperty = DependencyProperty.RegisterAttached(
"ShortcutKeys", typeof(ShortcutKeyCollection),
typeof(Command), new PropertyMetadata(onShortcutKeysChanged));
private static void onShortcutKeysChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var shortcuts = args.NewValue as ShortcutKeyCollection;
if (obj is UIElement && shortcuts != null)
{
var element = obj as UIElement;
shortcuts.ForEach(
sk => element.KeyUp += (s, e) => sk.Command.Execute(null));
}
}
public static ShortcutKeyCollection GetShortcutKeys(
DependencyObject obj)
{
return (ShortcutKeyCollection)obj.GetValue(ShortcutKeysProperty);
}
public static void SetShortcutKeys(
DependencyObject obj, ShortcutKeyCollection keys)
{
obj.SetValue(ShortcutKeysProperty, keys);
}
I know this attached property is working just fine since the event handlers are firing. However, the Command property of the ShortcutKey objects are not getting data bound. Here's the definition of ShortcutKey:
public class ShortcutKey : DependencyObject
{
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
"Key", typeof(Key), typeof(ShortcutKey), null);
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(ShortcutKey), null);
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
public class ShortcutKeyCollection : ObservableCollection<ShortcutKey> { }
The property that is getting bound to has its value set in the constructor of my view model, and its type is ICommand. So why isn't my Command property getting data bound? Also, have you found an effective way to debug data binding issues in Silverlight?
Edit:
At least one thing that was wrong was that ShortcutKey derived from DependencyObject instead of FrameworkElement, which is apparently the only root class that binding can be applied to. However, even after that change, the binding continued to not work properly.
You need to specify the Source of the Binding, since the DataContext is not inherited by members of the ObservableCollection.
edit:
Try setting the ShortcutKey.DataContext in onShortcutKeysChanged:
private static void onShortcutKeysChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var shortcuts = args.NewValue as ShortcutKeyCollection;
if (obj is FrameworkElement && shortcuts != null)
{
var element = obj as FrameworkElement;
ForEach(ShortcutKey sk in shortcuts)
{
sk.DataContext = element.DataContext;
element.KeyUp += (s, e) => sk.Command.Execute(null));
}
}
}
It looks like unless an object is inserted into the visual tree, no DataContext inheritance takes place, and thus no data binding works. I couldn't find a way to get the container's data context to be passed to the ShortcutKey objects, so as a workaround, I set up the binding in the code behind.
Hopefully someone else has a different answer that will show me how I won't have to resort to setting up this data binding in the code.

ag_e_parser_bad_property_value Silverlight Binding Page Title

XAML:
<navigation:Page ... Title="{Binding Name}">
C#
public TablePage()
{
this.DataContext = new Table()
{
Name = "Finding Table"
};
InitializeComponent();
}
Getting a ag_e_parser_bad_property_value error in InitializeComponent at the point where the title binding is happening. I've tried adding static text which works fine. If I use binding anywhere else eg:
<TextBlock Text="{Binding Name}"/>
This doesn't work either.
I'm guessing it's complaining because the DataContext object isn't set but if I put in a break point before the InitializeComponent I can confirm it is populated and the Name property is set.
Any ideas?
You can only use data binding on properties that are supported by DependencyProperty. If you take a look at the docs for TextBlock for example you will find that the Text property has a matching TextProperty public static field of type DependencyProperty.
If you look at the docs for Page you will find that there is no TitleProperty defined, the Title property is therefore not a dependency property.
Edit
There is no way to "override" this however you could create an attached property:-
public static class Helper
{
#region public attached string Title
public static string GetTitle(Page element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return element.GetValue(TitleProperty) as string;
}
public static void SetTitle(Page element, string value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(TitleProperty, value);
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.RegisterAttached(
"Title",
typeof(string),
typeof(Helper),
new PropertyMetadata(null, OnTitlePropertyChanged));
private static void OnTitlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Page source = d as Page;
source.Title = e.NewValue as string;
}
#endregion public attached string Title
}
Now your page xaml might look a bit like:-
<navigation:Page ...
xmlns:local="clr-namespace:SilverlightApplication1"
local:Helper.Title="{Binding Name}">
Add the following to MyPage.xaml.cs:
public new string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title",
typeof(string),
typeof(Page),
new PropertyMetadata(""));
Once you add this property (dependency property) to your code behind, your code will work as normal.

Resources