can anyone explain why the TextBlock inside my DataTemplate does not apply the style defined in my UserControl.Resources element, but the second TextBlock ('Test B') does?
I think it may have to do with a dependency property somewhere set to not inherit, but I can't be sure.
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Padding" Value="8 2" />
</Style>
</UserControl.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Padding does not apply-->
<TextBlock>Test A</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--Padding applies-->
<TextBlock>Test B</TextBlock>
</StackPanel>
Templates are considered as a boundary. Elements within the templates falls in this boundary range, and look up for the style with a matching target type ends within this range at runtime as a result the TextBlock outside will pickup the style and the one inside wont. like adminSoftDK said you should give the style an x:Key and then apply it as static resource it will work.
Related
I'm customizing a WPF DataGridCell style, but once I've done this double click to edit no longer works. I've also tried to manually call myDataGrid.BeginEdit, but nothing seems to happen. This is code similar to what I have:
<Style x:Key="MyCell" TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<TextBlock />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I assume the problem is that because I'm overriding the ControlTemplate, an editor no longer exists? If that's the case, is their a way I can sort this to use the default editor?
I'm using a subclassed TextBlock which sets different foreground colours for individual words in the content string. I couldn't figure out how to do that without replacing the ControlTemplate.
Instead of creating a custom ControlTemplate , you should create a custom column type and override the GenerateElement method to return an instance of your custom type:
public class CustomColumn : DataGridTextColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
return new YourCustomTextBlock();
}
}
Generally, you should avoid overriding ControlTemplates unless you really need to make substantial changes to the functionality or layout of a control (or if your building your own). You can usually do what you need with DataTemplates instead, which keep the underlying control intact, but customize the way your data is displayed.
For the DataGrid, there is a special type of column type meant just for this: DataGridTemplateColumn. Here's a quick example of how to use it:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<!--This is used when the cell is being displayed normally-->
<DataTemplate>
<TextBlock Text="{Binding SomePropertyOfYourRow}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<!--This is used when the cell is being edited (e.g. after double-click)-->
<DataTemplate>
<TextBox Text="{Binding SomePropertyOfYourRow}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
You can easily put your custom TextBlock into the CellTemplate and it will be use to display the contents of that cell.
I am using ToggleButtons in the ToolBar and I want to get and use them in the UI Automation tests, but when I check AutomationElement.Current for these buttons, its ClassName property is Button, while I would expect ToggleButton
The xaml is not starightforward, so I mention it here :
<ToolBar ItemsSource="{Binding}"/>
for the type that's in the ItemsSource I have a DataTemplate:
<DataTemplate DataType="{x:Type myViewModelType}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource MyToolBarElementTemplate}">
<ContentPresenter.Resources>
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource ThisStyleSetsWidthAndHeight}"/>
</ContentPresenter.Resources>
</ContentPresenter>
</DataTemplate>
the style is defined as follows:
<Style TargetType="{x:Type ButtonBase}" x:Key="ThisStyleSetsWidthAndHeight">
<Setter Property="styles:AttachedProperties.ContentWidth" Value="32"/>
<Setter Property="styles:AttachedProperties.ContentHeight" Value="32"/>
</Style>
and the content template looks like this:
<DataTemplate x:Key="MyToolBarElementTemplate" DataType="{x:Type myViewModelType}">
<ToggleButton x:Name="AutomationIdThatIGetOk">
...
</ToggleButton>
</DataTemplate>
I am a bit new to Automation Framework, I guess it has to do with all these templates and styles, but is there any way to get the proper AutomationPeer instance created for this ToggleButton?
...but when I check AutomationElement.Current for these buttons, its ClassName property is Button, while I would expect ToggleButton
Your expectation is wrong because the ToggleButtonAutomationPeer class actually returns the string "Button" from its GetClassNameCore() method: https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Automation/Peers/ToggleButtonAutomationPeer.cs,a58abe77888c16cd
So you are getting the proper instance.
I have the following setup:
<ListBox ItemSource="{Binding Targets}">
<ListBox.ItemTemplate>
<DataTemplate>
<view:ViewName />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What I am trying to accomplish is to dynamically decide which view to use at runtime, based on a property within the DataContext of the ListBox. In simple terms, I want to replace <view:ViewName> with a data binding that returns the proper view.
I use MEF to provide plug-ins for my app that may need to provide a custom view to display the items when appropriate. At design time I won't know all the possible view types (they may be dynamically loaded from a DLL) so a simple DataTemplateSelector won't do.
I have researched solutions but have come up empty.
Since you want to change templates based on a bound value, you can use a DataTrigger to determine the ContentTemplate of the ListBoxItem
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}"/>
<Style.Triggers>
<DataTrigger Property="{Binding SomeProperty}" Value="A">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}"/>
</DataTrigger>
</Style.Triggers>
</Style>
I find this better than using a DataTemplateSelector because it gets re-evaluated if the bound property changes, while a DataTemplateSelector does not.
If you want to change templates based on an object type, you can use Implicit DataTemplates. These are DataTemplates that define a DataType, but no x:Key, and they will be used anytime WPF tries to draw an object of the specified type.
For example, if you had this template defined in your <X.Resources> somewhere
<DataTemplate DataType="{x:Type models:ActionA}">
<views:ActionAView />
</DataTemplate>
you could then insert your Model object directly into the UI and WPF would draw it using the template you specified
<ContentControl Content="{Binding SomeIActionObject}" />
<ItemsControl ItemsSource="{Binding CollectionOfIActionObjects}" />
Update
You mentioned that you would be allowing users to create modules with additional Templates that get imported using MEF, so in that case you would probably be better off using an IValueConverter that look up the matching template within Application.Resources
For example, if the bound value equals "A", then the converter might search Application.Resources for a template named "TemplateA" and return it to the binding
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate"
Value="{Binding SomeProperty,
Converter={StaticResource MyTemplateConverter}}"/>
</Style>
Using the DataTemplateManager from this post You can do something like:
DataTemplateManager.RegisterDataTemplate<ViewModelType1, ViewType1>();
DataTemplateManager.RegisterDataTemplate<ViewModelType2, ViewType2>();
DataTemplateManager.RegisterDataTemplate<ViewModelType3, ViewType3>();
then you would remove the ItemTemplate from the ListBox:
<ListBox ItemSource="{Binding Targets}"/>
and in the ListBox ViewModel you could:
public void AddTargets()
{
Targets.Add(new ViewModelType1());
Targets.Add(new ViewModelType2());
Targets.Add(new ViewModelType3());
}
Then, each DataTemplate will be automatically used by WPF to render each corresponding ViewModel.
Also note that you can call DataTemplateManager.RegisterDataTemplate() at any time before showing the ListBox, so you can theoretically do that when loading the MEF parts.
Edit:
Based on your comment, you could create a single DataTemplate with a ContentPresenter to display the selected View according to a property in the ViewModel:
<DataTemplate DataType="{x:Type local:TargetViewModel}">
<ContentPresenter x:Name="MainContentPresenter" Content="{Binding}" ContentTemplate="{Binding YourProperty, Converter=SomeConverter}"/>
and inside the SomeConverter you should use the same technique as demonstrated in the post, to dynamically generate a DataTemplate.
I have what should be a really simple binding, but the problem I'm seeing is that instead of displaying the three companies (company_list is a List, where Company contains a company_id to bind to), I see the window pop up with only the first company_id in company_list. I have other bindings which seem to work fine, and in some other cases I see that I've used ItemSource instead of DataContext, but when I use that I get "Items collection must be empty before using ItemsSource". I've searched extensively for a simple answer to this in stackoverflow, msdn and elsewhere, and have seen mostly really complex solutions that I haven't been able to understand/apply.
When my window appears, it has:
CompanyA
where it should have:
CompanyA
CompanyB
CompanyC
which is the content of the company_list (yes, verified in debugger). Suggestions appreciated! Code and XAML follow.
ReadMasterCompanyList(); // populates a_state.company_list with 3 companies
// display company list dialog
CompanySelect cs_window = new CompanySelect();
cs_window.CompanyListView.DataContext = a_state.company_list;
// fails: cs_window.CompanyListView.ItemsSource = a_state.company_list;
cs_window.Show();
And the XAML from CompanySelect:
<Grid>
<ListView IsSynchronizedWithCurrentItem="True"
x:Name="CompanyListView"
SelectionMode="Single" SelectionChanged="CompanyListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="30"/>
</Style>
</ListView.ItemContainerStyle>
<ListViewItem Content="{Binding Path=company_id}"></ListViewItem>
</ListView>
</Grid>
I would set the ItemsSource of the ListView, rather than the DataContext, either in codebehind:
cs_window.CompanyListView.ItemsSource = a_state.company_list;
or with binding:
<ListView ItemsSource="{Binding company_list}">
And then set the ItemTemplate of the ListView instead.
...
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding company_id}" />
</DataTemplate>
</ListView.ItemTemplate>
...
I would also look into using the MVVM design pattern for testability and separation of concerns, and look at using PascalCase for your property names.
Also, unless you specifically wanted a ListView, I would use a ListBox.
First, set the DataContext only after cs_window.Show().
Second, the ListViewItem you have as a child in your ListView's XAML is why you're only seeing one.
Third, might work better (and would be more MVVM-ish) if you define ItemsSource in the XAML, like this:
<ListView ItemsSource="{Binding Path=company_list}" ...>
That's after making a_state the DataContext of the ListView's container or some other ancestor element.
The problem is, that you define one ListViewItem in your XAML code. You shouldn't do this.
Try something like this:
<Grid>
<ListView IsSynchronizedWithCurrentItem="True"
x:Name="CompanyListView"
SelectionMode="Single" SelectionChanged="CompanyListView_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Height" Value="30"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Content={Binding Path=company_id}/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
I have a TabControl control
<TabControl Name="Farms_myVillages"
ItemsSource="{Binding Villages}">
</TabControl/>
In the code behind I add some tabs dynamically to the TabControl as follows:
foreach (Village vill in Villages)
{
TabItem tab = new TabItem();
tab.Header = vill.Name;
VillageUserControl c = new VillageUserControl();
c.DataContext = vill;
tab.Content = c;
Farms_myVillages.Items.Add(tab);
}
where VillageUserControl is a UserControl that deal with the specified village. This code works fine and it gets the expected results...
The problem is that I don't want this to be in the code behind but just in the xaml itself.
I try this:
<TabControl Name="Farms_myVillages"
ItemsSource="{Binding Villages}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content">
<Setter.Value>
<u:VillageUserControl DataContext="{Binding}"/>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
After I run it, it throws an exception: "Specified element is already the logical child of another element. Disconnect it first."
Did I miss something? Please help me here...
You set the wrong thing, you should not modify the ItemContainerStyle but the TabControl.ItemTemplate for the header and TabControl.ContentTemplate for the content.
(The exception may have to do with the fact that in the style only one VillageUserControl is created, but the style applies to multiple tab items.)
Now it is working:
<TabControl Name="Farms_myVillages"
ItemsSource="{Binding Villages}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<u:VillageResources/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Your approach of not having this in code behind is right, instead of using ItemContainerStyle use ItemTemplate and ContentTemplate. You can have a look at this sample from Josh Smith for creating a tabs using Templates and Styles -
http://code.msdn.microsoft.com/mag200902MVVM/Release/ProjectReleases.aspx?ReleaseId=2026