Silverlight relativebinding ItemTemplate ListboxItem - Listbox - silverlight

I created programatically a class (I called it ViewGrid) so that I use an instance of it as ItemTemplate for my ListBox control; of course, it's my data template for the listboxitem....
Also, in my ViewGrid class, I got a dependency property called IsChecked and I want to keep it in sync with the ListBoxItem's IsSelected property. I noticed that in SL there no relativesource-findancestor-ancestortype support for binding as in WPF, still, I need to find a way to keep my IsChecked property synchronized with the IsSelected property of the internally generated ListBoxItem for my ListBox control. Can you help?

Here is a ListBox defined in XAML that uses the IsSelected property of each LitBoxItem to show or hide a button when selected. You just need to duplicate that Binding approach for the ListBoxItems you create in code. Either that, or create a UserControl with the appropriate ListBoxItem XAML, and insert instances of those UserControls into your ListBox.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="200" Height="120">
<StackPanel Margin="5">
<TextBlock Text="{Binding Name, Mode=OneWay}" />
<StackPanel Visibility="{Binding IsSelected, Mode=OneWay, Converter={StaticResource BoolToVisible}}">
<Button Content="Show Details" Click="OnDetailsClick" Tag="{Binding}" />
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Good luck,
Jim McCurdy
Face To Face Software and YinYangMoney

UPDATE: I revisited this and found a much better solution. My original one remains below, but the way I actually ended up solving this problem is via using the ViewGrid in a ControlTemplate instead of a DataTemplate. Then you can use the RelativeSource TemplatedParent binding to bind to the IsSelected property of the ListBox. So, add the following to the Resources of the listbox or your page or user control:
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel>
<ViewGrid IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>​
<!-- other controls may go here -->
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ORIGINAL:
So after seven years, you almost certainly don't need an answer to this anymore... however, I recently spent a morning wrestling with this issue and thought I'd give my solution in case any similar unfortunate ends up here.
First off, anyone who's using Silverlight 5 is in luck as AncestorType is apparently now available for RelativeSource, letting you bind directly to the IsSelected property of the ListBoxItem. For those of us stuck with 4 or below, the only real workaround I came up with was "faking" the binding via use of events in the code behind.
To do this, assume you have your YourView XAML with a ListBox named "lbYourListBox" which has its ItemsSource and SelectedItem properties bound to appropriate properties on a YourViewModel class, along with a ViewGrid in its ItemTemplate whose IsChecked property is not bound to anything. Then, in your code behind file, you wire up events as follows:
public YourView()
{
InitializeComponent();
this.Loaded += (sender, e) =>
{
((YourViewModel)this.DataContext).PropertyChanged += vm_PropertyChanged;
UpdateViewGrids();
};
}
// this part propagates changes from the view to the view model
private void viewGrid_Checked(object sender, RoutedEventArgs e)
{
var selectedVM = ((ViewGrid)sender).DataContext as SourceItemType;
((YourViewModel)this.DataContext).SelectedViewGridItem = selectedVM;
}
private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.Equals(e.PropertyName, "SelectedViewGridItem"))
{
UpdateViewGrids();
}
}
// this part propagates changes from the view model to the view
private void UpdateViewGrids()
{
var viewGrids = this.lbYourListBox.GetVisualDescendants().OfType<ViewGrid>();
var selectedVM = ((YourViewModel)this.DataContext).SelectedViewGridItem;
foreach (var grid in viewGrids)
{
grid.IsChecked = selectedVM == grid.DataContext;
}
}​
The viewGrid_Checked event handler should be wired up to the Checked event of the view grid in the ItemTemplate. The GetVisualDescendants() method comes from the Silverlight Toolkit.
Important caveats:
The ViewGrid.Checked event should not fire except for the unchecked->checked transition, and no more than one view grid should be able to be selected at once. If those two things aren't true, you'll have to make appropriate edits to ensure this code can't cause an infinite event-driven loop. (Of course, if you don't need two-way binding, you only need one of these event handlers and event ping-pong isn't a concern.)
I wrote this for a user control which had its data context set in XAML, which is why the event handler for the view model's PropertyChanged event is only assigned after the view is loaded. Depending on how and when your view and view model are bound to each other, you may have to assign that earlier/later/differently.
This won't work if the view grids aren't visible, GetVisualDescendants seems to ignore hidden/collapsed controls.

Related

Binding inside ContentControl not working

I'm building a graphical designer, based upon an article by Sukram in CodeProject. I'm now trying to extend it so that each item on the canvas binds to a different ViewModel object - i.e. I'm setting the DataContext for each item.
Every item on the designer is actually a ContentControl, into which is placed a different template (based upon which toolbox item was dragged onto the canvas). So I have a template containing a TextBox, and I have a ViewModel object containing a Name property, and I bind the Text property of the TextBox to the Name property of the ViewModel, and ... nothing. I've checked the visual tree with Snoop, and it confirms that the DataContext of the TextBox is the ViewModel object. Yet the TextBox remains empty. And if I modify the (empty) Text in the TextBox, the Name property in the ViewModel does not change. So it looks like the binding is not being applied (or has been removed somehow).
I've found a few posts which talk about the ContentControl messing around with the DataContext and Content properties, but I'm not sure how applicable they all are. The code sets the ContentControl.Content as follows:
newItem = new ContentControl();
ControlTemplate template = toolbox.GetTemplate();
UIElement element = template.LoadContent() as UIElement;
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = element;
newItem.DataContext = viewModel;
and the XAML for the template is:
<ControlTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</ControlTemplate>
Snoop shows that the TextBox has a DataContext, and if I Delve that DataContext I can see that it has a Name property whose value is "Bob". So why does the TextBox remain empty? Snoop allows me to change that Name property, but the TextBox remains empty.
What am I doing wrong?
A few more details. I've set the VS2010 Debug DataBinding option for the OutputWindow to Verbose, which seems to show that the binding is all being attempted before I set the DataContext. Is it possible that the change to the DataContext is not being recognised?
I've just found this post DataTemplate.LoadContent does not preserve bindings - apparently DataTemplate.LoadContent does not preserve bindings. So it looks like I have to write my own version of LoadContent().
I've realised that the template has come through a XamlWriter, which apparently strips all bindings. This wouldn't be helping.
I've not been able to fix the DataTemplate.LoadContent(), but I realised that I didn't actually need a DataTemplate, since the XamlWriter / XamlReader was already instantiating the UI element that I was after. I found a fix to make the XamlWriter write all the bindings here, and after that it all works.
Thanks for your help.
Maybe you need to tell the binding in the ControlTemplate to look at the TemplatedParent, as is mentioned in this thread?
<TextBox Text="{Binding Path=Name, RelativeSource={RelativeSource TemplatedParent}}"/>
Either that, or try to use a DataTemplate instead.
I can't test this at the moment, so I might just be guessing here.
I would use a DataTemplate, as bde suggests.
You are trying to put some UI on your own data (ViewModel), and this is what Data-Templates are meant for (ControlTemplate is usually what you use if you want to change how e.g. a Button looks).
Change your code to use ContentControl.ContentTemplate with a DataTemplate:
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Width="100">
<TextBox Text={Binding Name}/>
</Border>
</DataTemplate>
Code-behind:
newItem = new ContentControl();
//NOTE: .GetTemplate() needs to return a DataTemplate, and not a ControlTemplate:
newItem.ContentTemplate = toolbox.GetTemplate();
ViewModelItem viewModel = new ViewModelItem() { Name = "Bob" };
newItem.Content = viewModel;
newItem.DataContext = viewModel;

How to bind a Command to double-click on a row in DataGrid

I have developed a WPF UserControl that is intended to be used as a pick list as follows:
A DataGrid bound to a CollectionView of entities (e.g. of Employees)
A TextBox above the DataGrid that can be used to filter items displayed in the DataGrid.
I want to expose a Command that will be executed when the user double-clicks on a row in the DataGrid. The container can then react to this by doing something with the SelectedItem in the DataGrid.
So far I've tried to handle the double-click as follows:
<DataGrid IsReadOnly="True">
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="... />
</DataGrid.InputBindings>
...
However the double-click event still fires when the user clicks in the DataGrid header. I'd like to be able to limit it so that the Command is only executed when the double click is in the body of the DataGrid. Is there a declarative way to do this?
UPDATE
I'm just getting to grips with WPF and MVVM, and am really looking for guidance on how to implement low-level reusable components like this. Any general advice will also be gratefully received and upvoted. As it stands, I'm assuming I will want this UserControl to:
Expose a dependency property "SelectedItem" that is bound to the DataGrid's SelectedItem
Expose a RoutedEvent "ItemDoubleClick" or similar that is fired when the user double-clicks on a row.
Implement ICommandSource and call CommandHelpers.ExecuteCommandSource(this) from the row double-click event handler.
If code behind is not a problem:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="Loaded" Handler="Row_Loaded"/>
</Style>
</DataGrid.RowStyle>
private void Row_Loaded(object sender, RoutedEventArgs e)
{
var row = sender as DataGridRow;
row.InputBindings.Add(new MouseBinding(MyCommands.MyCommand,
new MouseGesture() { MouseAction = MouseAction.LeftDoubleClick }));
}
You can simply put the DataGrid into a Grid and define your InputBindings in the Grid. In the canExecute-definition, you should check, if a row is selected. That works for the KeyBinding as well, for example a custom Delete-Command.
<Grid>
<Grid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="... />
</Grid.InputBindings>
<DataGrid IsReadOnly="True">
...
</Grid>

In WPF, how do I find an element in a template that's switched in via a trigger?

I have a UserControl (not a lookless custom control) which, depending on some custom state properties, swaps in various ContentTemplates, all defined as resources in the associated XAML file. In the code-behind, I need to find one of the elements in the swapped-in ContentTemplates.
Now in a lookless control (i.e. a custom control), you simply override OnApplyTemplate then use FindName, but that override doesn't fire when the ContentTemplate gets switched by a trigger (...at least not for a UserControl. I haven't tested that functionality with a custom control.)
Now I've tried wiring up the Loaded event to the control in the swapped-in template, which does fire in the code-behind, then I simply store 'sender' in a class-level variable. However, when I try to clear that value by subscribing to the Unloaded event, that doesn't fire either because the tempalte gets swapped out, thus unwiring that event before it has a chance to be called and the control unloads from the screen silently, but I still have that hung reference in the code-behind.
To simulate the OnApplyTemplate functionality, I'm considering subscribing to the ContentTemplateChanged notification and just using VisualTreeHelper to look for the control I want, but I'm wondering if there's a better way, hence this post.
Any ideas?
For reference, here's a very-stripped-down example of the control I have. In this example, if IsEditing is true, I want to find the textbox named 'FindMe'. If IsEditing is false which means the ContentTemplate isn't swapped in, I want to get 'null'...
<UserControl x:Class="Crestron.Tools.ProgramDesigner.Controls.EditableTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Crestron.Tools.ProgramDesigner.Controls"
x:Name="Root">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox x:Name="FindMe"
Text="{Binding Text, ElementName=Root}" />
</DataTemplate>
<Style TargetType="{x:Type local:EditableTextBlock}">
<Style.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<TextBlock x:Name="TextBlock"
Text="{Binding Text, ElementName=Root}" />
</UserControl>
Aaaaaaand GO!
M
Unfortunately, there isn't a better way. You can override the OnContentTemplateChanged, instead of hooking up to the event.
You would need to use the DataTemplate.FindName method to get the actual element. The link has an example of how that method is used.
You would need to delay the call to FindName if using OnContentTemplateChanged though, as it is not applied to the underlying ContentPresenter immediately. Something like:
protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
this.Dispatcher.BeginInvoke((Action)(() => {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in OnContentTemplateChanged";
}), DispatcherPriority.DataBind);
}
Alternatively, you may be able to attach a handler to the LayoutUpdated event of the UserControl, but this may fire more often than you want. This would also handle the cases of implicit DataTemplates though.
Something like this:
public UserControl1() {
InitializeComponent();
this.LayoutUpdated += new EventHandler(UserControl1_LayoutUpdated);
}
void UserControl1_LayoutUpdated(object sender, EventArgs e) {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in UserControl1_LayoutUpdated";
}

How to get the SelectedItem from a toolkit DataGrid which is on a UserControl in WPF using the MVVM Pattern

My question if very similar to question "Exposing DataGrid SelectedItem in parent UserControl in WPF" which is here in this forum.
I have an XAML file which has two UserControls being used a a master-detail window. The first UserControl is just a DataGrid which holds some widgets and the second one holds the detail information of the selected widget. I am trying to user the MVVM pattern so I am trying to avoid using a RoutedEventHandler to handle the SelectionChanged event of the DataGrid. I created a RoutedEventHandler to test if my Detail-info UserControl would be populated with the selectedItem from the DataGrid and it did. So I proved that it worked.
Now, after removing the RoutedEventHandler and proceeding to use a SelectedItem Dependency Property to let the UserControl which displays the detail info nothing gets display on this UserControl when the DataGrid selection changes. My View needs to be aware of the changes. I read Josh Smith's article in which he provides an example using a listview.
I have read quite a few posts/blogs but most of them do not provide an answer or do not user the DataGrid in a UserControl.
In my view I set the DataContext of my window to the windowViewModel which returns a Collection of widgetViewModel and I also set the DataConext of a groupbox on the same window which holds the Detail widget info to the windowViewModel.SelectedItem dependency property.
here is the main code
<UserControl.Resources>
<Style x:Key="WidgetItemStyle" TargetType="{x:Type toolkit:DataGridRow}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</UserControl.Resources>
<Grid>
<toolkit:DataGrid
x:Name="dgWidgets"
DataContext="{StaticResource WidgetsList}"
ItemsSource="{Binding}"
SelectedItem="{Binding ElementName=WidgetsUserControl, Path=SelectedItem, Mode=TwoWay}"
SelectionUnit="FullRow"
SelectionChanged="dgWidgets_SelectionChanged"
IsSynchronizedWithCurrentItem="true"
ItemContainerStyle="{StaticResource WidgetItemStyle}"
>
<toolkit:DataGrid.Columns>
.... column defs
In the window which contains the two UserControls I have:
var viewModel //this variable holds the windowViewModel
this.DataContext = viewModel;
this.gBox.DataContext = viewModel.SelectedItem;
I have been struggling with this for quite some time and still do not know the problem is.
You are right there. I got this to work simply by binding the SelectedItem to a property "currentWidget" on my VM. When the value is changed, you know the user selected a new record in the master grid. Bind your detail control to the same property (if it contains all the details you need) and you are home.
XAML
SelectedItem="{Binding currentWidget, Mode=TwoWay}"...
VM Code
private Widget _currentWidget;
public Widget currentWidget
{
get { return _currentWidget; }
set
{
_currentWidget = value;
NotifyPropertyChanged("currentWidget");
}
}

Change the layout of a TreeView to looks like multiple ListBox [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I'm trying to change the layout of a databound treeview from this:
To this:
And of course selection must works properly:
Do you have any ideas about how to do that. I've been trying to change the template but I can't find out a way to have this behavior. Maybe a component already exists...
Thanks for your help !
This is difficult. It seems to need a HierarchicalDataTemplate, but because the behavior you want requires multiple ItemsControls, it is not going to work as expected. I don't think there is a way to create a TreeView template in XAML that will do this. Your best bet is to create a custom items control of some sort. You will probably need to do the items binding in code, rather than in XAML, because without the HierarchicalDataTemplate the XAML has no way of understanding nested relationships.
That being said, if you are guaranteed to only have 2 levels of nesting (as in your example), you could do this easily with the following mark-up:
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Window.Resources>
<StackPanel Orientation="Horizontal">
<ListBox Name="Level1" Width="150" Height="150"
ItemsSource="{Binding Collection}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level2" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level1, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level3" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level2, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
</StackPanel>
Where Collection is your root items collection and there is a property on each item called Children containing the child collection.
But I think what you are asking for is an items control that can support any number of nested levels, not just 2. So in that case, I would do this in code-behind. The binding will be the same- that is, at each level, the ListBox should be bound to the parent level's items. But you will obviously need to iterate and create one ListBox for each nested level.
I finally find a way out, but like you say Charlie, it involves creating ListBox:
I create a new CustomControl which inherits Control (I couldn’t use neither Selector or TreeView because I wouldn’t have been able to manage the SelectedItem property from the derived class)
In the template of this CustomControl is an ItemsControl. This ItemsControl has its ItemTemplate property set to a DataTemplate containing a ListBox.
The CustomControl has a Depth property of type int. This property indicates the number of ListBox that should be generated.
The CustomControl automatically databound ListBoxes together: each ListBox’s ItemsSource property is databound to the SelectedItem’s children property of the previous ListBox in the visual tree.
The CustomControl has a SelectedItem property and a SelectionChanged event (like Selector-derived class).
I added an IsReallySelected attached property to the ListBoxItem which are generated. This enables to databing an IsSelected property of the ViewModel class behind the control with the IsSelected of the ListBoxItem. I had to create an attached property because its value is true when the ListBoxItem is selected AND the parent ListBox has IsSelectionActive set to true.
I blogged about this solution (with source code) on my blog.
Its too bad I didn't notice this question before you went to all that work. It is easy to restyle a TreeView to appear this way: The only code required is a single very simple attached property, "VisibleWhenCurrentOf".
The way to do it is to:
Style TreeViewItem to include a ListBox in its ControlTemplate outside the ItemsPresenter.
Control the visibility of the TreeViewItem template using "VisibleWhenCurrentOf", so that a given item is only visible inside the ItemsPresenter if it is the current item within the ListBox.
Restyling details
Here is the XAML for the relevant templates:
<ControlTemplate TargetType="TreeView">
<DockPanel>
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
<ControlTemplate TargetType="TreeViewItem">
<DockPanel
local:VisibilityHelper.VisibleWhenCurrentOf="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor,HeaderedItemsControl,2}">
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
These two templates are identical except for the conditional visibilty. The way this works is that the "+" in front of the tree item becomes a ListBox, and all items except the one selected in the ListBox are hidden.
Your BoxesTreeViewBoxStyle should set a margin around the ListBox so they will space correctly. You can actually simplify this further by putting the ListBox property values in the style, but I find it more convenient to set them in the ControlTemplate so I can restyle the ListBox without having to remember these settings.
Attached property
Here is the code for the VisibleWhenCurrentOf attached property:
public class VisibilityHelper : DependencyObject
{
// VisibleWhenCurrentOf
public static object GetVisibleWhenCurrentOf(DependencyObject obj) { return (object)obj.GetValue(VisibleWhenCurrentOfProperty); }
public static void SetVisibleWhenCurrentOf(DependencyObject obj, object value) { obj.SetValue(VisibleWhenCurrentOfProperty, value); }
public static readonly DependencyProperty VisibleWhenCurrentOfProperty = DependencyProperty.RegisterAttached("VisibleWhenCurrentOf", typeof(object), typeof(VisibilityHelper), new UIPropertyMetadata
{
PropertyChangedCallback = (sender, e) =>
{
var element = sender as FrameworkElement;
if(e.OldValue!=null)
{
var oldView = e.OldValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
oldView.CurrentChanged -= UpdateVisibilityBasedOnCurrentOf;
if(e.NewValue==null) element.DataContextChanged -= UpdateVisibilityBasedOnCurrentOf;
}
if(e.NewValue!=null)
{
var newView = e.NewValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
newView.CurrentChanged += UpdateVisibilityBasedOnCurrentOf;
if(e.OldValue==null) element.DataContextChanged += UpdateVisibilityBasedOnCurrentOf;
}
UpdateVisibilityBasedOnCurrentOf(sender);
}
});
static void UpdateVisibilityBasedOnCurrentOf(object sender, DependencyPropertyChangedEventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender, EventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender)
{
var element = sender as FrameworkElement;
var source = GetVisibleWhenCurrentOf(element);
var view = source==null ? null : source as ICollectionView ?? CollectionViewSource.GetDefaultView(source);
var visible = view==null || view.CurrentItem == element.DataContext;
element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}
}
There is nothing complex here: Any time DataContext or the view's Current changes, visibilty is recomputed. The PropertyChangedCallback simply sets event handlers to detect these conditions and the UpdateVisibiltyBasedOnCurrentOf handler recomputes visibility.
Advantages of this solution
Since this solution is a real TreeView:
You get all the selection handling functionality for free.
It works with any number of tree levels.
You can use all the features of HierarchicalDataTemplate, including HeaderTemplate and HeaderTemplateSelector
You can use different ItemsSource bindings at each level rather than every collection requiring a "Children" proerty
It is a lot less code than a custom control

Resources