I have a UserControl that contains a TabControl that has an ItemTemplate that in turn has a Button. I want the user be able to change the content of that button, e.g. change it from TextBlock to Image.
A solution I thought of was to set the button's content from the Resources of the UserControl and overwrite the Resource by setting it on the ResourceDictionary of the entailing Window. Of course that does not work as StaticResource always resolves to the "closest" instance it can find.
I then thought of modifying the resource in the constructor of my UserControl, depending on some property. But it seems, one cannot change a resource. Below is a close sample showing the idea with a simple ListBox in a Window in which I try to change "What" to "How".
How would you approach this?
<Window.Resources>
<TextBlock x:Key="key" Text="What: " x:Shared="false" />
</Window.Resources>
<Grid Margin="10">
<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StaticResource ResourceKey="key" />
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TextBlock tb = FindResource("key") as TextBlock;
tb.Text = "How: ";
List<string> items = new List<string>();
items.Add("Item 1");
items.Add("Item 2");
items.Add("Item 3");
lbTodoList.ItemsSource = items;
}
}
Instead of trying to override a resource, you should just treat it like any other XAML data-binding templating issue.
Instead of:
<StaticResource ResourceKye="key" />
Do something like this:
<TextBlock Text="{Binding LabelText}" />
In your UserControl set the default value of LabelText to "What:" and then allow the user to override the value. The data binding will take care of the rest.
If you want to have more dynamic content, then use a ContentControl instead and have properties for Content, ContentTemplate, and even ContentTemplateSelector depending on what you need to do.
<ContentControl
Content="{Binding MyContent}"
ContentTemplate="{Binding MyContentTemplate}"
ContentTemplateSelector="{Binding MyContentTemplateSelector}" />
This opens up a lot of flexibility.
Related
This question already has answers here:
Stop TabControl from recreating its children
(4 answers)
WPF TabControl - Preventing Unload on Tab Change?
(3 answers)
Closed 8 years ago.
I wrote some WPF application with MVVM pattern that holds a TabControl bound to collection of "TabViewModelItem".
The main window XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:XcomSavesGameEditor.ViewModel"
x:Class="XcomSavesGameEditor.MainWindow"
xmlns:Views="clr-namespace:XcomSavesGameEditor.View"
Title="X-COM Saved Game Editor" Height="650" Width="850" Background="#FF1B0000">
<Window.DataContext>
<ViewModel:TabsManagerViewModel/>
</Window.DataContext>
<Grid>
... (some not relevant code removed for clearity of question) ...
<TabControl x:Name="myTabs" Background="Black" Margin="0,25,0,0" BorderThickness="0,0,0,0" BorderBrush="Black" ItemsSource="{Binding Tabs}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type ViewModel:Tab0a_FileSaveData_ViewModel}">
<Views:Tab0a_FileSaveData_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab0b_Summary_ViewModel}">
<Views:Tab0b_Summary_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab1_Research_ViewModel}">
<Views:Tab1_Research_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab2_Engineering_ViewModel}">
<Views:Tab2_Engineering_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab3_Barracks_ViewModel}">
<Views:Tab3_Barracks_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab4_Hangar_ViewModel}">
<Views:Tab4_Hangar_View />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:Tab5_SituationRoom_ViewModel}">
<Views:Tab5_SituationRoom_View />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<Grid Margin="0">
<Border Margin="0,0,0,0"
Background="Black"
BorderBrush="Black"
BorderThickness="0,0,0,0" Padding="0,0,0,0">
<StackPanel Orientation="Horizontal"
Margin="0,0,0,0">
<Image Name ="tabImage" Source="{Binding TabImage_Disabled}" />
</StackPanel>
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter TargetName="tabImage" Property="Source" Value="{Binding TabImage_Enabled}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<Grid>
<Grid.Background>
<ImageBrush ImageSource="{Binding TabImage_Background}"/>
</Grid.Background>
<UniformGrid>
<ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{Binding TabContents}" />
</UniformGrid>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
and the ViewModel that hold collection of tab is code:
public sealed class TabsManagerViewModel : ViewModelBase
{
private ObservableCollection<TabViewModelItem> _tabs;
public ObservableCollection<TabViewModelItem> Tabs
{
get { return _tabs; }
set
{
_tabs = value;
RaisePropertyChanged("Tabs");
}
}
public TabsManagerViewModel()
{
Tabs = new ObservableCollection<TabViewModelItem>();
Tabs.Add(new TabViewModelItem { TabName = "File_Save_Data", TabImage_Enabled = _aEnabledTabImages[(int)enum_Tabs.SaveFileData_Tab], TabImage_Disabled = _aDisabledTabImages[(int)enum_Tabs.SaveFileData_Tab], TabImage_Background = _aBackgroundTabImages[(int)enum_Tabs.SaveFileData_Tab], TabContents = new Tab0a_FileSaveData_ViewModel() });
Tabs.Add(new TabViewModelItem { TabName = "Summary", TabImage_Enabled = _aEnabledTabImages[(int)enum_Tabs.Summary_Tab], TabImage_Disabled = _aDisabledTabImages[(int)enum_Tabs.Summary_Tab], TabImage_Background = _aBackgroundTabImages[(int)enum_Tabs.Summary_Tab], TabContents = new Tab0b_Summary_ViewModel() });
... (rest of code removed for clearity of question)
}
}
So basically it's tab control that is bound to a collection of "TabViews".
and based of the object data type it's showing View1 or View2.
Note: View1 & View 2 are UserControls, each bound to it's own ViewModel.
This concept works fine.
Now where is the problem you my ask ?
My problem is:
EVERY time I click on another tab & then return to same tab, I get that specifc tab ViewModel constructor called again, where as I would expect the ViewModel object would remain.
This is problem, because it cause me to lose any modification made on that page, when I toggle between tabs. and since the ctor is called everytime, over & over, I can't even use the VIewModel to store this information.
My questions are:
1) Is there any way I can prevent the TabControl to dispose ViewModel objects when tab is inactive ? Meaning to pre-create all ViewModel's object & not dispose them when hidden ?
2) What "workarounds" using this concept exist, that allow me to store "visual tree" of the given tab, so if i navigate away from it & then re-open it, it will store all information on it (such as selected check boxes, written text, etc.)
Would appreciate any help on matter.
regards,
Idan
Solution to problem is extending TabControl and replacing default behaviour so it will not unload old tabs.
The final solution (with include's both control & control template) is #
Stop TabControl from recreating its children
Thanks for Shoe for pointing me in right direction that lead to final solution :)
I have the similar problem and i came up with this solution
void OnAddWorkSpace(object Sender, EventArgs e)
{
WorkspaceViewModel loclViewModel = (e as WorkSpaceEventArgs).ViewModel;
DataTemplate loclTemplate = (DataTemplate)Resources[new DataTemplateKey(loclViewModel.GetType())];
if (loclTemplate != null)
{
DXTabItem loclTabItem = new DXTabItem();
loclTabItem.Content = loclTemplate.LoadContent();
(loclTabItem.Content as DependencyObject).SetValue(DataContextProperty, loclViewModel);
loclTabItem.HeaderTemplate = (DataTemplate)FindResource("WorkspaceItemTemplate");
loclTabItem.Header = (loclTabItem.Content as DependencyObject).GetValue(DataContextProperty);
MainContentTabs.Items.Add(loclTabItem);
}
}
I have created an event handler in my ViewModel and my View subscribes to that. My ViewModel objects get added to a collection. Now, whenever a ViewModel is added, my MainViewModel will call this even handler.
Here, we need to find the DataTemplate that would have been defined for the DataType of the ViewModel that we are adding. Once we get hold of that, we can create a tab item and then load the content from the datatemplate.
Since i am using DevExpress TabControl, i have created DXTabItem. A TabItem should also work the same.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text={Binding Path=Content} Foreground={Binding Path=TextColor}/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
Hi, I'm developing a book reader application in WP8. I have a stoy with a list of paragraph which I use ListBox to display. Each paragraph content is binding to a textblock as you can see in my code. In Paragraph class, I define a field call TextColor to bind the foreground color of textblock to it. Now, each time user change the color, I have to loop through all paragraph in story and change the value of TextColor. Is there any way to separately bind 2 different property (ie. the Foreground and the Text) of a ListboxItem to different source> So I'll only have to change the Foreground one time. Thank
KooKiz solution is pretty good. However, if you don't want to include any properties to manage foreground colors, or any visual property for that matter, at all you can simply make it a static resource and bind to that instead where you can modify them independently of your model.
For example, you could define a ForegroundResouces class and add in different types of foregrounds your app needs.
In App.xaml
<Application.Resources>
<local:ForegroundResouces xmlns:local="clr-namespace:YOUR-NAMESPACE" x:Key="ForegroundResouces" />
</Application.Resources>
Then define your class
public class ForegroundResouces {
public static Brush TitleForeground { get; set; }
public static Brush ContentForeground { get; set; }
// ...
}
Then define your binding
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock
Text={Binding Path=Content}
Foreground={Binding Path=ContentForeground, Source={StaticResource ForegroundResouces} />
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
Then you can simply change the foreground by modifying your static properties
ForegroundResources.ContentForeground = new SolidBrush(Colors.Red);
You could sort of make different set of themes but this solution is probably only worth it if you have more than one visual property to manage.
There is ways to specify a different source for your bindings. For instance, you can use ElementName to point at your listbox and retrieve its datacontext:
<ListBox x:Name="MyList" ItemsSource="{Binding Path=Paragraphs}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text="{Binding Path=Content}" Foreground="{Binding Path=DataContext.TextColor, ElementName=MyList}"/>
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
But in your case, it's probably easier to just set the Foreground property on the parent list. It will be automatically applies to all child controls:
<ListBox x:Name="MyList" ItemsSource="{Binding Path=Paragraphs}" Foreground="{Binding Path=TextColor}">
<ListBox.ItemTemplate>
<DataTemplate>
<Textblock Text="{Binding Path=Content}" />
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
I want to bind "Header" of the TabItem properties on MVVM way.
I bind the "ItemsSource" property of the "XamTabControl" to a list of view models (List<MyTabItem> MyTabItem is a viewmodel too).
Here is the XAML Code
<igWindows:XamTabControl
Height="198"
HorizontalAlignment="Left"
Margin="0,54,0,0"
ItemsSource="{Binding Tabs}"
Name="xamTabControl1"
VerticalAlignment="Top"
Width="651">
<!-- this is the body of the TabItem template-->
<igWindows:XamTabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Header}" />
</DataTemplate>
</igWindows:XamTabControl.ItemTemplate>
<igWindows:XamTabControl.ContentTemplate>
<!-- this is the body of the TabItem template-->
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</igWindows:XamTabControl.ContentTemplate>
</igWindows:XamTabControl>
Here is the view model.
private ObservableCollection<TabItem> tabs;
public ObservableCollection<TabItem> Tabs
{
get
{
return tabs;
}
set
{
tabs = value;
NotifyPropertyChanged("Tabs");
}
}
To display the tab header, I have inserted a text block inside the ItemTemplate in XamlTabControl. I wanna display the header by using the "Header" property of the TabItemEx property instead of using text block.
And I wanna do this to "CloseButtonVisibility" property too.
I got the answer from the stackoverflow. Please look at this post
I need some quick help which is a road blocker for me now. I have Button in ItemsControl and I need to perform some task on Button click. I tried adding Command to Button in ItemsControl DataTemplate but its not working. Can anyone suggest how to proceed further.
<UserControl.Resources>
<DataTemplate x:key="mytask">
<TextBox Grid.Row="5" Grid.Column="2" Text="{Binding Path=PriorNote}" Grid.ColumnSpan="7" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,5" Width="505" Foreground="Black"/>
<StatusBarItem Grid.Row="2" Grid.Column="8" Margin="8,7,7,8" Grid.RowSpan="2">
<Button x:Name="DetailsButton" Command="{Binding CommandDetailsButtonClick}">
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding ListStpRules}"
ItemTemplate="{StaticResource myTaskTemplate}" Background="Black"
AlternationCount="2" >
</ItemsControl>
</Grid>
and in ViewModel I have implemented code for Command. And its not working. Please suggest any solution for me to proceed further
The DataContext of each item in your ItemsControl is the item in the collection the ItemsControl is bound to. If this item contains the Command, your code should work fine.
However, this is not usually the case. Typically there is a ViewModel containing an ObservableCollection of items for the ItemsControl, and the Command to execute. If this is your case, you'll need to change the Source of your binding so it looks for the command in ItemsControl.DataContext, not ItemsControl.Item[X]
<Button Command="{Binding
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.MyCommand}" />
If your ViewModel has a property of type ICommand you can bind the Button's Command property to that:
XAML:
<DataTemplate DataType="{x:Type my:FooViewModel}">
<Button Content="Click!" Command="{Binding Path=DoBarCommand}" />
</DataTemplate>
C#:
public sealed class FooViewModel
{
public ICommand DoBarCommand
{
get;
private set;
}
//...
public FooViewModel()
{
this.DoBarCommand = new DelegateCommand(this.CanDoBar, this.DoBar);
}
}
Read this:
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Implement a class similar to RelayCommand in the above article. Would make your further MVVM coding easier. :-)
Just a guess. Is CommandDetailsButtonClick defined in a ViewModel, which is DataContext of your UserControl (the one with ListStpRules property)?
DataContext of button in ItemTemplate is an item from ListStpRules, and if you command is not there then binding won't find it.
You can check diagnostic messages from wpf in Output window while debugging your application. It writes there if it can not resolve binding.
I have a combobox defined as follows:
<ComboBox x:Name="cboDept" Grid.Row="0" Margin="8,8,8,8" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource cvsCategories}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Width="Auto" Height="Auto">
<sdk:Label Content="{Binding CategoryID}" Height="20" />
<sdk:Label Content="{Binding CategoryName}" Height="20" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
It works fine. However, once I select an item in the list, I want a different template to be applied to the combobox selected item being shown to the user (the item shown after the disappearance of popup). In the above case, I want only CategoryName to be displayed in the ComboBox once I select the respective item.
Can anyone let me know on how to achieve this?
thanks
What you need to do is create a ResourceDictionary containing a few defined templates yourself. In the below, ComboBoxTemplateOne and ComboBoxTeplateTwo are user controls that are set out to display the combobox in the manor you want.
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate x:Key="TemplateOne">
<local:ComboBoxTemplateOne />
</DataTemplate>
<DataTemplate x:Key="TemplateTwo">
<local:ComboBoxTemplateTwo />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
You will then need to create your own class that inherits from ContentControl "DataTemplateSelector", overriding OnContentChanged
Protected Overrides Sub OnContentChanged(ByVal oldContent As Object, ByVal newContent As Object)
MyBase.OnContentChanged(oldContent, newContent)
Me.ContentTemplate = SelectTemplate(newContent, Me)
End Sub
You will then need to create another class that inherits from the above DataTemplateSelector which overrides SelectTemplate ("TemplateSelectorClass"), which will return the DataTemplate defined above ("TemplateOne" or "TemplateTwo").
Also in this derived class, you will need to define a property for each of the templates you have
Public Property ComboboxTemplateOne As DataTemplate
Then head back to your XAML and n the blow XAML
<local:TemplateSelectorClass ComboboxTemplateOne="{StaticResource TemplateOne}" Content="{Binding Path=ActiveWorkspace}>
This should work, as it is effectively doing the same work as setting the "DataTemplate" property in WPF (which doesn't exist in SilverLight)
I realise there are a fair few steps here and its quite fiddly, but hopefully this will get you there. Any questions just shout.