I have a usercontrol named ItemGrid with a button with an image, I moved the image to a controltemplate so I would be able to size it right:
<Button x:Name="btnOrder" Click="btnOrder_Click" HorizontalAlignment="Right" Width="48" Height="48" Margin="0,0,0,100">
<Button.Template>
<ControlTemplate>
<Image x:Name="imgOrder" Source="/Images/dark/appbar.food.png" Stretch="None"/>
</ControlTemplate>
</Button.Template>
</Button>
In the MainPage I want to set the right image depending on the currently selected them (Dark / Light)
private void detecttheme()
{
Visibility v = (Visibility)Resources["PhoneLightThemeVisibility"];
if (v == System.Windows.Visibility.Visible)
{
uri = new Uri("/Images/light/appbar.food.png", UriKind.Relative);
imgSource = new BitmapImage(uri);
ItemGrid.imgOrder.Source = imgSource;
}
else ....
That gives me: UserControls.ItemGrid' does not contain a definition for 'imgOrder' after I moved imgOrder to the controltemplate
I've tried to use findname but that gives a nullreference exception too for img
//Use FindName because we cannot directly reference the image because it's in a control template
Image img = ItemGrid.FindName("imgOrder") as Image;
img.Source = imgSource;
I also tried putting the findname in the OnApplyTemplate of the control but that doesn't seem to get fired at all?
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Image i = this.FindName("imgOrder") as Image;
}
I hope somebody has an answer to this?
Kind regards,
Mike
I think in this case you can find it by using the VisualTreeHelper class - there's a thread on this in WPF here, but in case you don't feel like reading it:
make this method:
/// <summary>
/// Finds a Child of a given item in the visual tree.
/// </summary>
/// <param name="parent">A direct parent of the queried item.</param>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="childName">x:Name or Name of child. </param>
/// <returns>The first parent item that matches the submitted type parameter.
/// If not matching item can be found,
/// a null parent is being returned.</returns>
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
and use it to find your image:
Image img = FindChild<Image>(btnOrder, "imgOrder");
Hopefully that works (I'll admit I haven't tested this yet)...and as for why OnApplyTemplate() doesn't work, I believe it's only called if you subclass the Button to make your own Custom button.
Related
I am new to WPF and am trying to use the Ribbon control.
I have a single tab in the application, and wish to hide the header but still show the tab itself.
I have been experimenting with various properties and styles, but I have only been able to hide the entire tab.
I've tried: ribbontab visibility, ribbontab.header visibility, setting hidden in TabHeaderItemCollection, applying style xaml to ribbontabheader element in ribbontab, experimenting with tabheadertemplate property, and generally sifting through the api looking for anything that may be relevant.
Google only turns up how to hide the whole tab.
Any other ideas?
I managed to hide both the tab headers and the application menu by shifting the control 47 pixels upwards...
<r:Ribbon Margin="0,-47,0,0" DockPanel.Dock="Top" x:Name="ribbon">
Note: you can hide just the application menu and not the tabs by doing this...
<r:Ribbon DockPanel.Dock="Top" x:Name="ribbon">
<r:Ribbon.ApplicationMenu>
<r:RibbonApplicationMenu Visibility="Collapsed" />
</r:Ribbon.ApplicationMenu>
Hiding just the tab headers, I can't quite do. I did get pretty close by overriding the ribbon class as follows...
class RibbonNoTab : Ribbon
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var ctrl = this.GetDescendants<Grid>().FirstOrDefault();
if (ctrl != null)
{
ctrl.RowDefinitions[1].Height = new GridLength(0, System.Windows.GridUnitType.Pixel);
}
}
}
The GetDescendants extension method just searches the visual tree for something of a specified type. It was taken from here: http://blog.falafel.com/finding-controls-by-type-in-silverlight-and-wpf/
The only issue with the above method was that there looks like a 1 pixel high bar remaining. You have to look pretty closely to see it!
Piggybacking off the other answer, there are a few ways you can do it without using a custom derived class
public class RibbonBehavior
{
public static bool GetHideRibbonTabs(DependencyObject obj)
{
return (bool)obj.GetValue(HideRibbonTabsProperty);
}
public static void SetHideRibbonTabs(DependencyObject obj, bool value)
{
obj.SetValue(HideRibbonTabsProperty, value);
}
// Using a DependencyProperty as the backing store for HideRibbonTabs. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HideRibbonTabsProperty =
DependencyProperty.RegisterAttached("HideRibbonTabs", typeof(bool), typeof(RibbonBehavior), new UIPropertyMetadata(false,OnHideRibbonTabsChanged));
public static void OnHideRibbonTabsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d == null || d.GetType() != typeof(Ribbon)) return;
(d as Ribbon).Loaded += ctrl_Loaded;
}
static void ctrl_Loaded(object sender, RoutedEventArgs e)
{
if (sender == null || sender.GetType() != typeof(Ribbon)) return;
Ribbon _ribbon = (Ribbon)sender;
var tabGrid = _ribbon.GetDescendants<Grid>().FirstOrDefault();
tabGrid.RowDefinitions[1].Height = new GridLength(0, System.Windows.GridUnitType.Pixel);
foreach (Line line in _ribbon.GetDescendants<Line>())
line.Visibility = Visibility.Collapsed;
}
}
then in your xaml:
<Ribbon SelectedIndex="0" a:RibbonBehavior.HideRibbonTabs="true">
Which will give you a nice square ribbon box with no tab:
Leaving out the code collapsing the lines leaves a little blip where the tab used to be, which was bothering me.
You could also put that loaded code straight into the code behind if you're not using MVVM, or in an EventToCommand if you are. This way is less reusable, but it's the same results.
EDIT: Here's the code for the GetDescendant methods
public static class VisualTreeExtensions
{
/// <summary>
/// Gets children, children’s children, etc. from
/// the visual tree that match the specified type
/// </summary>
public static List<T> GetDescendants<T>(this DependencyObject parent)
where T : UIElement
{
List<T> children = new List<T>();
int count = VisualTreeHelper.GetChildrenCount(parent);
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (child is T)
{
children.Add((T)child);
}
children.AddRange(child.GetDescendants<T>());
}
return children;
}
else
{
return new List<T> { };
}
}
/// <summary>
/// Gets children, children’s children, etc. from
/// the visual tree that match the specified type and elementName
/// </summary>
public static List<T> GetDescendants<T>(this DependencyObject parent, string elementName)
where T : UIElement
{
List<T> children = new List<T>();
int count = VisualTreeHelper.GetChildrenCount(parent);
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (child is T && (child is FrameworkElement)
&& (child as FrameworkElement).Name == elementName)
{
children.Add((T)child);
}
children.AddRange(child.GetDescendants<T>(elementName));
}
return children;
}
else
{
return new List<T> { };
}
}
/// <summary>
/// Gets the first child, child’s child, etc.
/// from the visual tree that matches the specified type
/// </summary>
public static T GetDescendant<T>(this DependencyObject parent)
where T : UIElement
{
List<T> descendants = parent.GetDescendants<T>();
if (descendants.Count > 0)
{
return descendants[0];
}
else
{
return null;
}
}
/// <summary>
/// Gets the first child, child’s child, etc. from
/// the visual tree that matches the specified type and elementName
/// </summary>
public static T GetDescendant<T>(this DependencyObject parent, string elementName)
where T : UIElement
{
List<T> descendants = parent.GetDescendants<T>(elementName);
if (descendants.Count > 0)
{
return descendants[0];
}
else
{
return null;
}
}
/// <summary>
/// Gets the first parent, parent’s parent, etc. from the
/// visual tree that matches the specified type
/// </summary>
public static T GetAntecedent<T>(this DependencyObject root)
where T : UIElement
{
if (root == null)
{
return null;
}
if (root is T)
{
return (T)root;
}
else
{
DependencyObject parent = VisualTreeHelper.GetParent(root);
if (parent == null)
{
return null;
}
else
{
return parent.GetAntecedent<T>();
}
}
}
/// <summary>
/// Gets the first parent, parent’s parent, etc. from the
/// visual tree that matches the specified type and elementName
/// </summary>
public static T GetAntecedent<T>(this DependencyObject root, string elementName)
where T : UIElement
{
if (root == null)
{
return null;
}
if (root is T && (root is FrameworkElement)
&& (root as FrameworkElement).Name == elementName)
{
return (T)root;
}
else
{
DependencyObject parent = VisualTreeHelper.GetParent(root);
if (parent == null)
{
return null;
}
else
{
return parent.GetAntecedent<T>(elementName);
}
}
}
}
In relation to flobadob's answer:
Shifting the control up 47 pixels is quite an interesting solution.
If you want to retain the line that is normally above the ribbon menu, shift the control, and then add a line in code. The following will work:
<ribbon:XamRibbon Grid.Row="1" x:Name="Ribbon" ApplicationMenuMode="Office2010" AllowMinimize="False" Margin="0,-49,0,0">
...
</ribbon:XamRibbon>
<Rectangle Grid.Row="0" HorizontalAlignment="Stretch" Fill="White" Height="5"/>
<Separator Grid.Row="0" />
Replace the white fill with the color of your background. Note also that the ribbon is Grid row 1, whereas the later placed line is grid row 0.
I have an issue with WPF ListBox in TabControl. ListBox resets it's scrollbar position to 0 when I change tabs. Here is repro code:
<TabControl x:Name="_tabs">
<TabItem Header="1">
<ListBox ItemsSource="{Binding}" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
</TabItem>
<TabItem Header="2">
<ListBox ItemsSource="{Binding}" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
</TabItem>
</TabControl>
_tabs.DataContext = Enumerable.Range(1, 300).ToArray();
When the window opens I open the second tab, scroll list somewhere to the middle, return to the first tab and then open the second again. For some reason the list is scrolled to the top.
Why this happens? Have I made some stupid mistake?
The default behavior of WPF is to unload items which are not visible, which includes unloading TabItems which are not visible. This means when you go back to the tab, the TabItem gets re-loaded, and anything not bound (such as a scroll position) will get reset.
There was is a good site here which contains code to extend the TabControl and stop it from destroying it's TabItems when switching tabs, however the site appears to be down atm.
Here's the code I use. It initially was from that site, although I've made some changes to it. It preserves the ContentPresenter of TabItems when switching tabs, and uses it to redraw the TabItem when you go back to the page. It takes up a bit more memory, however I find it better on performance since the TabItem no longer has to re-create all the controls that were on it.
// Extended TabControl which saves the displayed item so you don't get the performance hit of
// unloading and reloading the VisualTree when switching tabs
// Obtained from http://eric.burke.name/dotnetmania/2009/04/26/22.09.28
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : System.Windows.Controls.TabControl
{
// Holds all items, but only marks the current tab's item as visible
private Panel _itemsHolder = null;
// Temporaily holds deleted item in case this was a drag/drop operation
private object _deletedObject = null;
public TabControlEx()
: base()
{
// this is necessary so that we get the initial databound selected item
this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
/// <summary>
/// if containers are done, generate the selected item
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
UpdateSelectedItem();
}
}
/// <summary>
/// get the ItemsHolder and generate any children
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
UpdateSelectedItem();
}
/// <summary>
/// when the items change we remove any generated panel children and add any new ones as necessary
/// </summary>
/// <param name="e"></param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (_itemsHolder == null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
_itemsHolder.Children.Clear();
if (base.Items.Count > 0)
{
base.SelectedItem = base.Items[0];
UpdateSelectedItem();
}
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
// Search for recently deleted items caused by a Drag/Drop operation
if (e.NewItems != null && _deletedObject != null)
{
foreach (var item in e.NewItems)
{
if (_deletedObject == item)
{
// If the new item is the same as the recently deleted one (i.e. a drag/drop event)
// then cancel the deletion and reuse the ContentPresenter so it doesn't have to be
// redrawn. We do need to link the presenter to the new item though (using the Tag)
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
int index = _itemsHolder.Children.IndexOf(cp);
(_itemsHolder.Children[index] as ContentPresenter).Tag =
(item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
}
_deletedObject = null;
}
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
_deletedObject = item;
// We want to run this at a slightly later priority in case this
// is a drag/drop operation so that we can reuse the template
this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
new Action(delegate()
{
if (_deletedObject != null)
{
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
this._itemsHolder.Children.Remove(cp);
}
}
}
));
}
}
UpdateSelectedItem();
break;
case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not implemented yet");
}
}
/// <summary>
/// update the visible child in the ItemsHolder
/// </summary>
/// <param name="e"></param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectedItem();
}
/// <summary>
/// generate a ContentPresenter for the selected item
/// </summary>
void UpdateSelectedItem()
{
if (_itemsHolder == null)
{
return;
}
// generate a ContentPresenter if necessary
TabItem item = GetSelectedTabItem();
if (item != null)
{
CreateChildContentPresenter(item);
}
// show the right child
foreach (ContentPresenter child in _itemsHolder.Children)
{
child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
}
}
/// <summary>
/// create the child ContentPresenter for the given item (could be data or a TabItem)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
ContentPresenter CreateChildContentPresenter(object item)
{
if (item == null)
{
return null;
}
ContentPresenter cp = FindChildContentPresenter(item);
if (cp != null)
{
return cp;
}
// the actual child to be added. cp.Tag is a reference to the TabItem
cp = new ContentPresenter();
cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
cp.ContentTemplate = this.SelectedContentTemplate;
cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
cp.ContentStringFormat = this.SelectedContentStringFormat;
cp.Visibility = Visibility.Collapsed;
cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
_itemsHolder.Children.Add(cp);
return cp;
}
/// <summary>
/// Find the CP for the given object. data could be a TabItem or a piece of data
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
ContentPresenter FindChildContentPresenter(object data)
{
if (data is TabItem)
{
data = (data as TabItem).Content;
}
if (data == null)
{
return null;
}
if (_itemsHolder == null)
{
return null;
}
foreach (ContentPresenter cp in _itemsHolder.Children)
{
if (cp.Content == data)
{
return cp;
}
}
return null;
}
/// <summary>
/// copied from TabControl; wish it were protected in that class instead of private
/// </summary>
/// <returns></returns>
protected TabItem GetSelectedTabItem()
{
object selectedItem = base.SelectedItem;
if (selectedItem == null)
{
return null;
}
if (_deletedObject == selectedItem)
{
}
TabItem item = selectedItem as TabItem;
if (item == null)
{
item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
}
return item;
}
}
The TabControl template I usually use looks something like this:
<Style x:Key="TabControlEx_NoHeadersStyle" TargetType="{x:Type local:TabControlEx}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type localControls:TabControlEx}">
<DockPanel>
<!-- This is needed to draw TabControls with Bound items -->
<StackPanel IsItemsHost="True" Height="0" Width="0" />
<Grid x:Name="PART_ItemsHolder" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Great answer! Cheers Rachel! I can't comment on existing posts so have added my own answer.
I found the above managed to solve the problem. However I also found it necessary to add:
public TabControlEx()
{
Loaded += delegate { UpdateSelectedItem(); };
}
to get the initial selected tab to load correctly - the ItemContainerGenerator had no contents and so GetSelectedTabItem failed to return a TabItem. Presumably this is something to do with rendering which has yet to happen when the template is applied.
I found this problem manifested itself only when binding the ItemsSource and SelectedItem of a tab control - in an application I am working on we have recently switched to using this so we could programmatically switch tabs to do a bit of custom navigation.
The Tab Item contents were originally specified by hand eg:
<TabItem Header="blah">
<someControl/>
</TabItem>
With that setup changing the selected item was fine (didn't reload tabs on selection) although it was only ever done by wpf. This seems to suggest that this behaviour can be turned off in the default control but only when not binding to a list of items (although I didn't confirm this to be the case).
Another solutions I attempted were changing the ItemSource binding Mode to OneTime but this did not solve the problem (we aren't using an INotifyCollectionChanged collection here anyway).
I'm struggling a bit with keyboard navigation in the Telerik WPF RadCarousel. If i click outside an item, but within the carousel-control, keyboard naviation works as expected (I can switch between items using the left and right keyboard arrows), but if I click an item within the RadCarousel, keyboard navigation is gone. How can I get the RadCarousel to handle keyboard navigation when an item in the carousel has focus?
Additional things I want to accomplish:
Automatically show the SelectedItem as the "front-item" in the carousel.
Automatically select the "front-item" when navigating through the carousel.
My RadCarousel binding is set up as follows:
<ScrollViewer CanContentScroll="true">
<telerik:RadCarousel Name="carousel" HorizontalScrollBarVisibility="Hidden"
ItemsSource="{Binding Path=Templates}"
ItemTemplate="{StaticResource template}"
SelectedItem="{Binding Path=SelectedTemplateAndFolder}" />
</ScrollViewer>
Edit:
By using Snoop, I can see that the "CarouselScrollViewer" has focus when the scrolling is working. Selecting an item causes the RadCarousel to get focus (and the navigation to stop working).
I got the most import things to work, which was the keyboard navigation and moving the selected item to the front of the carousel.
Keyboard navigation:
private void Carousel_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
CarouselScrollViewer scrollViewer = FindChild<CarouselScrollViewer>(this.carousel, null);
scrollViewer.Focus();
}
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
Moving selected item into center of carousel:
private void Carousel_SelectionChanged(object sender, SelectionChangeEventArgs e)
{
this.carousel.BringDataItemIntoView(this.carousel.CurrentItem);
}
Is there a way to prevent the Tab Unload/Reload when a tab changes in a WPF tab control? Or if that is not possible, is there a recommended method for caching the tabs contents so they don't have to be regenerated with each tab change?
For example, one tab's UI is completely customizable and stored in the database. When the user selects an object to work on, the items in the customized layout get populated with that object's data. Users expect a minor delay on initial load or when retrieving data, but not when changing back and forth between tabs, and the delay when changing tabs is very noticeable.
I found a workaround here: https://web.archive.org/web/20120429044747/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28
Edit: This is the corrected link:
http://web.archive.org/web/20110825185059/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28
It basically stores the ContentPresenter of the tab and loads that up when switching tabs instead of redrawing it. It was still causing the delay when dragging/dropping tabs since that was an remove/add operation, however with some modifications I got that to go away as well (ran the Remove code at a lower dispatcher priority then the Add code, so the add operation had a chance to cancel the Remove operation and use the old ContentPresenter instead of drawing a new one)
Edit: The link above appears to no longer work, so I'll paste a copy of the code here. It's been modified a bit to allow dragging/dropping, but it should still work the same way.
using System;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Collections.Specialized;
// Extended TabControl which saves the displayed item so you don't get the performance hit of
// unloading and reloading the VisualTree when switching tabs
// Obtained from http://eric.burke.name/dotnetmania/2009/04/26/22.09.28
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class TabControlEx : System.Windows.Controls.TabControl
{
// Holds all items, but only marks the current tab's item as visible
private Panel _itemsHolder = null;
// Temporaily holds deleted item in case this was a drag/drop operation
private object _deletedObject = null;
public TabControlEx()
: base()
{
// this is necessary so that we get the initial databound selected item
this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
/// <summary>
/// if containers are done, generate the selected item
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
UpdateSelectedItem();
}
}
/// <summary>
/// get the ItemsHolder and generate any children
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel;
UpdateSelectedItem();
}
/// <summary>
/// when the items change we remove any generated panel children and add any new ones as necessary
/// </summary>
/// <param name="e"></param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (_itemsHolder == null)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
_itemsHolder.Children.Clear();
if (base.Items.Count > 0)
{
base.SelectedItem = base.Items[0];
UpdateSelectedItem();
}
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
// Search for recently deleted items caused by a Drag/Drop operation
if (e.NewItems != null && _deletedObject != null)
{
foreach (var item in e.NewItems)
{
if (_deletedObject == item)
{
// If the new item is the same as the recently deleted one (i.e. a drag/drop event)
// then cancel the deletion and reuse the ContentPresenter so it doesn't have to be
// redrawn. We do need to link the presenter to the new item though (using the Tag)
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
int index = _itemsHolder.Children.IndexOf(cp);
(_itemsHolder.Children[index] as ContentPresenter).Tag =
(item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
}
_deletedObject = null;
}
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
_deletedObject = item;
// We want to run this at a slightly later priority in case this
// is a drag/drop operation so that we can reuse the template
this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
new Action(delegate()
{
if (_deletedObject != null)
{
ContentPresenter cp = FindChildContentPresenter(_deletedObject);
if (cp != null)
{
this._itemsHolder.Children.Remove(cp);
}
}
}
));
}
}
UpdateSelectedItem();
break;
case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not implemented yet");
}
}
/// <summary>
/// update the visible child in the ItemsHolder
/// </summary>
/// <param name="e"></param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectedItem();
}
/// <summary>
/// generate a ContentPresenter for the selected item
/// </summary>
void UpdateSelectedItem()
{
if (_itemsHolder == null)
{
return;
}
// generate a ContentPresenter if necessary
TabItem item = GetSelectedTabItem();
if (item != null)
{
CreateChildContentPresenter(item);
}
// show the right child
foreach (ContentPresenter child in _itemsHolder.Children)
{
child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
}
}
/// <summary>
/// create the child ContentPresenter for the given item (could be data or a TabItem)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
ContentPresenter CreateChildContentPresenter(object item)
{
if (item == null)
{
return null;
}
ContentPresenter cp = FindChildContentPresenter(item);
if (cp != null)
{
return cp;
}
// the actual child to be added. cp.Tag is a reference to the TabItem
cp = new ContentPresenter();
cp.Content = (item is TabItem) ? (item as TabItem).Content : item;
cp.ContentTemplate = this.SelectedContentTemplate;
cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
cp.ContentStringFormat = this.SelectedContentStringFormat;
cp.Visibility = Visibility.Collapsed;
cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
_itemsHolder.Children.Add(cp);
return cp;
}
/// <summary>
/// Find the CP for the given object. data could be a TabItem or a piece of data
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
ContentPresenter FindChildContentPresenter(object data)
{
if (data is TabItem)
{
data = (data as TabItem).Content;
}
if (data == null)
{
return null;
}
if (_itemsHolder == null)
{
return null;
}
foreach (ContentPresenter cp in _itemsHolder.Children)
{
if (cp.Content == data)
{
return cp;
}
}
return null;
}
/// <summary>
/// copied from TabControl; wish it were protected in that class instead of private
/// </summary>
/// <returns></returns>
protected TabItem GetSelectedTabItem()
{
object selectedItem = base.SelectedItem;
if (selectedItem == null)
{
return null;
}
if (_deletedObject == selectedItem)
{
}
TabItem item = selectedItem as TabItem;
if (item == null)
{
item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
}
return item;
}
}
Just to add to this, I had a similar problem and managed to solve it by caching a user control that represented the contents of a tab item in the code behind.
In my project I have a tab control that is bound to a collection (MVVM). However the first tab is an overview that shows a summary of all of the other tabs in a list view. The problem I was having was that whenever a user moves their selection from an item tab to the overview tab, the overview is redrawn with all of the summary data, which can take 10-15 seconds depending on the number of items in the collection. (note their is no reloading of actual data from a db or anything, it is purely the drawing of the summary view that was taking the time).
What I wanted was for this loading of the summary view to only occur once when the data context is first loaded and any subsequent switching between tabs to be instantaneous.
Solution:
Classes involved:
MainWindow.xaml - The main page containing the tab control.
MainWindow.xaml.cs - Code behind for above.
MainWindowViewModel.cs - View model for the above view, contains the collection.
Overview.xaml - User control that draws the overview tab item content.
OverviewViewModel.cs - View model for the above view.
Steps:
Replace the datatemplate in 'MainWindow.xaml' that draws the overview tab item with a blank user control named 'OverviewPlaceholder'
Make the reference to 'OverviewViewModel' public within 'MainWindowViewModel.cs'
Add a static reference to 'Overview' in 'MainWindow.xaml.cs'
Add an event handler to the loaded event of user control 'OverviewPlaceholder', within this method instantiate the static reference to 'Overview' only if it is null, set the datacontext of this reference to the 'OverviewViewModel' reference within the current datacontext (that is 'MainWindowViewModel') and set the place holder's content to be the static reference to 'Overview'.
Now the overview page is only drawn once because each time it is loaded (i.e. the user clicks onto the overview tab), it puts the already rendered, static user control back onto the page.
I have a really simple solution to avoid the tab reload on tab change,
use a contentPresenter in the tabItem instead of the content property.
e.g.(in MVVM style)
replace
<TabItem Header="Tab1" Content="{Binding Tab1ViewModel}" />
by
<TabItem Header="Tab1">
<ContentPresenter Content="{Binding Tab1ViewModel}" />
</TabItem>
Can I pass events like key strokes to another control in Silverlight?
Imagine I'm in a custom control that contains a Textbox and a Treeview.
I'm listening to a Key event for the TextBox. When the user pushes the Arrow Up or Arrow Down key, I want the Treeview to behave as if it's itself who received that event, i.e. it should move the current selection up or down. The user shouldn't lose focus on the TextBox though so that they can continue typing.
Is this possible? I don't want to set the selection manually on the Treeview because it has no easy MoveSelectionUp() or MoveSelectionDown() method, so I would have to duplicate that functionality which is not so trivial especially when the tree is databound and loads nodes on demand.
Maybe you could consider using the Mediator Design Pattern to achieve this?
I can only think of two ways to "forward" key events:
Your way: Register key event handlers and process events in your own code and doing the state manipulation on the target control via properties, methods, extensions (and whatever API is offered by the target control).
This works, but it is basically reimplementing stuff that someone else has already written - inside the target control-, and you always run the risk of forgetting a corner case.
Inherit from the target control and add your input textbox to the ControlTemplate:
When your control is really quite similar to an existing control, so you can honestly say "MyFoo IS_A SomeTargetControl" (and then anyway chances are you will want to offer DependencyProperties for further customization that are just a copy of what is already present at the target control class) you should totally use inheritance.
Your TextBox won't set the ArrowUp and ArrowDown key events to handled, so the inherited key handling code will take care of them and will manipulate the selection corretly.
In the meantime I solved my special scenario by creating MoveSelectionUp() and MoveSelectionDown() extension methods on the TreeView. I copied the implementation over from some private methods in the Toolkit's control code and made slight changes were private or protected methods were accessed. Thanks to all the extensions methods that are available in the toolkit now it's not so difficult anymore.
Because it is largely not mine, I hereby provide the code below if future visitors stumble upon the same issue.
I'd leave this question open however because I'd still like to know, in a more general fashion, if events can be forwarded in the DependencyObject framework.
TreeViewExtensions:
using System;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
static public class TreeViewExtensions
{
static public void SetSelectedContainerIfValid(this TreeView self, TreeViewItem itm)
{
Contract.Requires(self != null);
if (itm != null)
{
self.SetSelectedContainer(itm);
}
}
static public void MoveSelectionUp(this TreeView self)
{
Contract.Requires(self != null);
var itm = self.GetSelectedContainer();
if (itm == null)
{
self.SetSelectedContainerIfValid(self.GetContainers().LastOrDefault());
}
else
{
self.SetSelectedContainerIfValid(itm.GetContainerAbove());
}
}
static public void MoveSelectionDown(this TreeView self)
{
Contract.Requires(self != null);
var itm = self.GetSelectedContainer();
if (itm == null)
{
self.SetSelectedContainerIfValid(self.GetContainers().FirstOrDefault());
}
else
{
self.SetSelectedContainerIfValid(itm.GetContainerBelow());
}
}
}
TreeViewItemExtensions:
using System;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Controls;
static public class TreeViewItemExtensions
{
static public TreeViewItem GetContainerBelow(this TreeViewItem self)
{
return TreeViewItemExtensions.GetContainerBelow(self, true);
}
/// <summary>
/// Find the next focusable TreeViewItem below this item.
/// </summary>
/// <param name="recurse">
/// A value indicating whether the item should recurse into its child
/// items when searching for the next focusable TreeViewItem.
/// </param>
/// <returns>The next focusable TreeViewItem below this item.</returns>
static public TreeViewItem GetContainerBelow(this TreeViewItem self, bool recurse)
{
Contract.Requires(self != null);
// Look for the next item in the children of this item (if allowed)
if (recurse && self.IsExpanded && self.HasItems)
{
TreeViewItem item = self.ItemContainerGenerator.ContainerFromIndex(0) as TreeViewItem;
if (item != null)
{
return item.IsEnabled ?
item :
item.GetContainerBelow(false);
}
}
// Look for the next item in the siblings of this item
ItemsControl parent = self.GetParentTreeViewItem() as ItemsControl ?? self.GetParentTreeView();
if (parent != null)
{
// Get the index of this item relative to its siblings
TreeViewItem item = null;
int index = parent.ItemContainerGenerator.IndexFromContainer(self);
int count = parent.Items.Count;
// Check for any siblings below this item
while (index++ < count)
{
item = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
if (item != null && item.IsEnabled)
{
return item;
}
}
// If nothing else was found, try to find the next sibling below
// the parent of this item
TreeViewItem parentItem = self.GetParentTreeViewItem();
if (parentItem != null)
{
return parentItem.GetContainerBelow(false);
}
}
return null;
}
/// <summary>
/// Find the last focusable TreeViewItem contained by this item.
/// </summary>
/// <returns>
/// The last focusable TreeViewItem contained by this item.
/// </returns>
static public TreeViewItem GetLastContainer(this TreeViewItem self)
{
Contract.Requires(self != null);
TreeViewItem item = self;
TreeViewItem lastItem = null;
int index = -1;
// Walk the children of the current item
while (item != null)
{
// Ignore any disabled items
if (item.IsEnabled)
{
// If the item has no children, it must be the last
if (!item.IsExpanded || !item.HasItems)
{
return item;
}
// If the item has children, mark it as the last known
// focusable item so far and walk into its child items,
// starting from the last item and moving toward the first
lastItem = item;
index = item.Items.Count - 1;
}
else if (index > 0)
{
// Try searching for the previous item's sibling
index--;
}
else
{
// Stop searching if we've run out of children
break;
}
// Move to the item's previous sibling
item = lastItem.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
}
return lastItem;
}
/// <summary>
/// Find the previous focusable TreeViewItem above this item.
/// </summary>
/// <returns>
/// The previous focusable TreeViewItem above this item.
/// </returns>
static public TreeViewItem GetContainerAbove(this TreeViewItem self)
{
Contract.Requires(self != null);
ItemsControl parent = self.GetParentTreeViewItem() as ItemsControl ?? self.GetParentTreeView();
if (parent == null)
{
return null;
}
// Get the index of the current item relative to its siblings
int index = parent.ItemContainerGenerator.IndexFromContainer(self);
// Walk the previous siblings of the item to find a focusable item
while (index-- > 0)
{
// Get the sibling
TreeViewItem item = parent.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
if (item != null && item.IsEnabled)
{
// Get the last focusable descendent of the sibling
TreeViewItem last = item.GetLastContainer();
if (last != null)
{
return last;
}
}
}
return parent as TreeViewItem;
}
}