Custom TabItem in TabControl - wpf

I've created CustomTabItem which inherits from TabItem and i'd like to use it while binding ObservableCollection in TabControl
<TabControl ItemsSource="{Binding MyObservableCollection}"/>
It should like this in XAML, but i do not know how change default type of the output item created by TabControl while binding.
I've tried to create converter, but it has to do something like this inside convertin method:
List<CustomTabItem> resultList = new List<CustomTabItem>();
And iterate through my input ObservableCollection, create my CustomTab based on item from collection and add it to resultList...
I'd like to avoid it, bacause while creating CustomTabItem i'm creating complex View and it takes a while, so i don't want to create it always when something change in binded collection.
My class extends typical TabItem and i'd like to use this class in TabControl instead of TabItem.
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type local:CustomTabItem}">
<Setter Property="MyProperty" Value="{Binding xxx}"/>
</Style>
</TabControl.ItemContainerStyle>
Code above generates error that Style cannot be applied to TabItem.
My main purpose is to use in XAML my own CustomTabItem and bind properties... Just like above...
I've also tried to use
<TabControl.ItemTemplate/>
<TabControl.ContentTemaplte/>
But they are just styles for TabItem, so i'll still be missing my properties wchich i added in my custom class.

You will need to create a custom class derived from TabControl and override GetItemForContainerOverride to return your custom TabItem:
protected override DependencyObject GetContainerForItemOverride()
{
return new CustomTabItem();
}

Related

Custom Control with Resource Dictionary

I have created a custom control for which I would like to be able to add resources. Right now I am enclosing the custom control in a ContentControl as below. Would it be possible to change MyControl such that I can have a ResourceDictionary inside of MyControl? Maybe like an attached DependencyProperty?
<ContentControl>
<ContentControl.Resources>
...
</ContentControl.Resources>
<utils:MyControl
Propery_A="{Binding Some_A}"
Propery_B="{Binding Some_B}"
DeleteButtonContent="{StaticResource customDeleteButtonContent}"
/>
</ContentControl>
Would it be possible to change MyControl such that I can have a ResourceDictionary inside of MyControl?
I may have misunderstood your issue but you could just inherit from FrameworkElement or any of its descendants, like for example Control:
public class MyControl : Control
...
Then you can set its Resources property like you set it for any other control:
<utils:MyControl
Propery_A="{Binding Some_A}"
Propery_B="{Binding Some_B}"
DeleteButtonContent="{StaticResource customDeleteButtonContent}"
<utils.MyControl.Resources>
...
</utils.MyControl.Resources>
</utils:MyControl>
No need to wrap your control in a ContentControl.

Create Silverlight Control that can be used as the layout root

How can I create a control in silverlight that can be used as the layout root, but still have a "Template" property so I can wrap the users content inside another control using a style?
My current implementation is close, it takes the content that the user places in the control and wraps it but the user has to put a grid or panel in if there is multiple controls for the content.
--Update --
This is the code I'm using that will not work as the rootlayout for multiple children unless the user puts a grid around their content. If I inherit from Grid or Panel I get an error about the DefaultStyleKey property not being available.
public class BusyControl :ContentControl
{
public BusyControl()
{
this.DefaultStyleKey = typeof(BusyControl);
}
}
<Style TargetType="local:BusyControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BusyControl">
<telerik:RadBusyIndicator DisplayAfter="0:0:0.5" IsBusy="{Binding IsBusy}" BusyContent="{Binding BusyMessage}">
<ContentPresenter Content="{TemplateBinding Content}" Margin="{TemplateBinding Padding}"/>
</telerik:RadBusyIndicator>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is how I want the user to be able to use my new control with out having to wrap their content in a panel or grid.
<cdc:BusyControl x:Name="BusyControl">
<some:Control x:Name="Control1" />
<some:Control x:Name="Control2" />
</cdc:BusyControl>
Seems to me that what you want is to derive your control from an ItemsControl not a ContentControl. In any ControlTemplate that you use you can place the controls using an ItemsPresenter instead of the ContentPresenter you would have used in a ContentControl.
You can have your control inherit from Panel if you want it to be able to have multiple children. You will have to handle laying out the panel's child controls if you do this. See this MSDN article on creating custom panels.
You can then specify your Template and stick the user content into the template.
I think that if you want multiple controls, i.e. children, as content then you have to use some sort of panel, which is the base class for the .Children property.
Not sure if this is applicable to your situation. I had trouble grasping exactly what's going on in your question. Maybe you can make a custom user control that inherits from ContentControl. As you may or may not know, custom user controls need a default style key. With a custom user control you need to define a template in the default style. Now the template can have a ContentControl somewhere inside of it and its content property should be template binding to the contentcontrol.content property. Or you can override the OnContentChanged function and do whatever you want in that override function (like put a single object in the control by itself... or for multiple objects create a new grid/panel and then set the objects as the grid/panels children for the user and then do what ever it is you are doing with the grid/panel. You would have to set/bind the content property on your new control. Make sense?
I don't know about your error with the default style key, and i don't have any telerik controls, but couldn't you just inherit from your telerik busy indicator? Would something like this work for you, (or put you on the right track).
protected override void OnContentChanged( object oldContent, object newContent )
{
//I dont know how you are assigning content,
//but i would say if it's IEnumerable and count is > 1 it should use your panel
var newMultiContent = newContent as System.Collections.IEnumerable;
if ( newMultiContent!=null && newMultiContent.Cast<object>().Count()>1)
{
var myNewContentContainer = new StackPanel();//or grid or whatever
myNewContentContainer.Children.Clear();
//add children
foreach (var item in newMultiContent.OfType<UIElement>())
myNewContentContainer.Children.Add(item);
//instead of the old content that wasn't what you wanted, use the new content container
base.OnContentChanged( oldContent, myNewContentContainer );
//or maybe try this and call the base method at the beginning...
Content = myNewContentContaint
}
else
base.OnContentChanged( oldContent, newContent );
}

WPF: Create a custom control without rewriting the ControlTemplate

Hey, I am creating a Custom Control i WPF inheriting from the ListView. However, I want it to look exactly as the already existing ListView.
Is there a way To use the default ListView Template in a Custom Control without rewriting it in xaml? I do have a Generic.xaml file with the new control added, but I should no need to rewrite the template code.
Thanks
EDIT: I also want to keep it as DRY as possible without repeating (making a mess) the code.
If you subclass the ListView, them your subclassed control will use the ListView Template. That's it! You do not have to do anything!
The Template used by a control is defined by its DefaultStyleKey dependency property. If you want to change the template of your control, set this property as follows:
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl),
new FrameworkPropertyMetadata(typeof(MyControl)));
However, if you do not set this property, it will use the value set by the superclass.
I think the problem is that you have used "Add New Item" => "Custom Control" to create you control then changed the class it extends. Instead of doing this, just add a new C# class and extend ListView.
<Style TargetType="{x:Type local:MyControl}" BasedOn={StaticResource {x:Type ListView}}" />

How to connect a Button in a Silverlight ListItem DataTemplate, in a ResourceDictionary (Styles.xaml), with a handler?

OK, so the situation is I'm defining an ItemTemplate for a ListBox in a ResourceDictionary (Styles.xaml). The ListBoxItem Template looks something like this:
<ControlTemplate TargetType="ListBoxItem">
<Button Command="{Binding Path=DoSomeCommand}" Content="Test" />
</ControlTemplate>
Now wherever this template is used, I'd like to have this button's click bind to an available ViewModel command to handle it.
However this does not work as is, I've also tried this:
<ControlTemplate TargetType="ListBoxItem">
<Button Command="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DoSomeCommand}" Content="Test" />
</ControlTemplate>
But still no dice.
A simple example that does work is if you define the template in the control (resources) that is using it, and just use an event handler (the same handler for all generated XAML.
Any ideas or thoughts on the best way to accomplish this? I figure this must be a common scenario: the goal is just to allow the user to interact with the items in the ListBox.
Thanks!
OK I think I answered my own question :
The solution seems to be to use 'nested' ViewModels here:
In other words, rather than have my ListBox bind directly to a collection of DTOs/business objects (as I was doing above) I instead created a simple ViewModel to wrap each DTO, and have the command on it, rather than on the original, top-level VM.
So the bound collection now looks like this:
TestItems = new ObservableCollection<ItemVM> ()
{
new ItemVM(),
new ItemVM(),
new ItemVM()
};
And each ItemVM just wraps the DTO, and has the command:
public class ItemVM : INotifyPropertyChanged
{
public ItemVM ()
{
this.MyCommand = new DelegateCommand<string> ( TheCommand );
}
public ICommand MyCommand { get; private set; }
public MyBusinessObject BizObj;
}
And voila, no need for a RelativeSource, and we have a reusable template complete with commands.
Long answer: Reference to a TextBox inside a DataTemplate
Short answer: Use Prism Commands or Blend Behaviours.

Create TabItems with differing content at runtime based on templates in WPF

I'm writing an application with WPF and part of it involves managing for the user various files which are used configure custom, in-house devices. I need to be able to manipulate different types of configurations in tabs in the same TabControl, meaning that the content of the TabItems must be dynamically generated. I'd like to do this with ControlTemplates, but I haven't been successful in getting a working template yet. I have a ControlTemplate called "pendantConfigurationTabItemTemplate" defined in my Window resources, and I use the following code to apply the template (which contains a named item I need to access) to the TabItems and add them to their parent TabControl :
<ControlTemplate x:Key="pendantConfigurationTabItemTemplate" TargetType="TabItem">
<StackPanel Orientation="Vertical">
<my:PendantConfigurationFileEditor x:Name="configurationEditor"/>
<StackPanel Style="{StaticResource defaultOkCancelButtonsContainerStyle}">
<Button Style="{StaticResource defaultOkCancelButtonStyle}"/>
<Button Style="{StaticResource defaultOkCancelButtonStyle}" Click="OkButton_Click"/>
</StackPanel>
</StackPanel>
</ControlTemplate>
Code behind :
TabItem ConfigTab = new TabItem();
switch (ConfigFile.Device)
{
case DeviceType.PENDANT:
{
ControlTemplate TabTemplate = Resources["pendantConfigurationTabItemTemplate"] as ControlTemplate;
ConfigTab.Template = TabTemplate;
ConfigTab.ApplyTemplate();
object Editor = TabTemplate.FindName("configurationEditor", ConfigTab);
PendantConfigurationFileEditor ConfigFileEditor = Editor as PendantConfigurationFileEditor;
ConfigFileEditor.PendantConfiguration = DeviceConfig;
break;
}
default:
/* snipped */
return;
}
ConfigTab.Header = ConfigFile.ConfigurationName;
this.EditorTabs.Items.Add(ConfigTab);
this.EditorTabs.SelectedIndex = this.EditorTabs.Items.Count - 1;
However, whenever I run the program, no tabs get added to the tab control, instead the tab control (seemingly) gets replaced or covered by the content of the template. Can somebody please help me out with this ?
Effectively, what I want to do is use the WPF templates as TabItem factories
TabControl.ItemsSource plus DataTemplates is effectively the "templates as factories" solution you are asking for, but it demands a slightly different approach to your current one.
Rather than writing procedural code to create and template TabItems and calling Items.Add, use the ItemsSource property and data binding. This will cause WPF to create a TabItem for each object in the ItemsSource. You can then use ContentTemplateSelector to select appropriate templates for the object displayed on this tab, according to whatever criteria are appropriate (e.g. the Device property) -- though in this case you will be using DataTemplates rather than ControlTemplates.
Your selector will look something like this:
public class DeviceTypeSelector : DataTemplateSelector
{
public DataTemplate PendantTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override SelectTemplate(object item, DependencyObject container)
{
ConfigFile cf = (ConfigFile)item;
switch (cf.Device)
{
case DeviceType.Pendant: return PendantTemplate;
default: return DefaultTemplate;
}
}
}
and will be instantiated in XAML like this:
<local:DeviceTypeSelector x:Key="dts"
PendantTemplate="{StaticResource pt}"
DefaultTemplate="{StaticResource dt}" />
(where pt and dt are suitable DataTemplates defined elsewhere in the resources).
Finally, your TabControl will look like this:
<TabControl Name="EditorTabs"
ContentTemplateSelector="{StaticResource dts}" />
and you set it up as EditorTabs.ItemsSource = myConfigFiles; (or better still let it acquire the ItemsSource in XAML from the DataContext).
You'll also want to set up the headers of the TabItems: to do this, use TabControl.ItemContainerStyle, with a Setter for the Header property. I think this would look something like this:
<TabControl ...>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding ConfigurationName}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
(You can also inline the ContentTemplateSelector, by the way: I broke it out into a resource mostly so as to show things in smaller chunks.)

Resources