Trouble setting styles for nested elements - c#

I'm having trouble setting up nested styles in WPF. I don't know if I'm doing things the 'right way' - but I'll describe what I'm attempting to do and provide some code.
I'm working with Kinect and am using a ContentControl to represent a Kinect object in my ViewModel. I declare it as such:
<ContentControl Content="{Binding Kinect}" ContentTemplate="{StaticResource SkeletonTemplate}" />
I then set up the SkeletonTemplate as such:
<DataTemplate x:Key="SkeletonTemplate">
<Grid>
<ContentControl Content="{Binding HandLeft}" ContentTemplate="{StaticResource JointTemplate}"/>
<ContentControl Content="{Binding HandRight}" ContentTemplate="{StaticResource JointTemplate}"/>
</Grid>
</DataTemplate>
Lastly, I set up a JointTemplate as such:
<DataTemplate x:Key="JointTemplate">
<Ellipse Fill="Red" Margin="0,0,620,460">
<Ellipse.RenderTransform>
<TranslateTransform X="{Binding Path=Position.X}" Y="{Binding Path=Position.Y}" />
</Ellipse.RenderTransform>
</Ellipse>
</DataTemplate>
Everything is all hunky dory except I'd really like to be able to set a Style at highest level that allows me to style the nested elements. For example, I want to be able to do this:
<ContentControl Content="{Binding Kinect}" Style="{DynamicResource ShrunkBlueSkeleton}" ContentTemplate="{StaticResource SkeletonTemplate}" />
And immediately apply a set of rules to the nested elements. Make the ellipses Blue, apply a ValueConverter to the ContentControls in the SkeletonTemplate to scale them to a smaller part of the screen, etc.
I'm having a bitch of a time getting it working, and I'm not sure if I've even set up everything up in the 'right' way to this point.
Certainly I could re-declare a ton of different ContentTemplates that style in all the different ways I need to, but that is much more obviously bad style.
Any pros out there that can lend a hand?
I could paste the Style attempts I've made thus far, but I'm rather convinced they won't help the discussion.

The root feature that you are missing is that Style's setters works on Properties and not on Path (a la Binding) - which I believe is what you would like to have.
There are technical reasons for Microsoft did that: Style works on specific styles and not on compound elements. Similar styling frameworks (most notably: CSS) also support "setters" just for one element (although, arguably, the select is much superior).

Related

c# WPF XAML RelativeSource reference to ComboBox.Resources item

I am working on a reusable template for a WPF ComboBox. I am able to dynamically change the font color, background, and border by using these references in the template:
Foreground="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=Foreground}"
BorderBrush="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=BorderBrush}"
Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=Background}"
These were easy since I had properties I could reference.
<ComboBox Margin="90,62,0,0" Height="26" Width="302"
HorizontalAlignment="Left" VerticalAlignment="Top"
Focusable="False" IsReadOnly="True" MaxDropDownHeight="202"
Foreground="White" Background="SteelBlue" BorderBrush="White"
Style="{StaticResource ComboBoxFlatStyle}"
ItemContainerStyle="{StaticResource ComboBoxItemFlatStyle}">
What I would like to do is reference the following in a similar manner:
<ComboBox.Resources>
<SolidColorBrush x:Key="ComboBoxHighlightBrush" Color="RoyalBlue" />
</ComboBox.Resources>
Is it possible to reference a resource inside the control? I really want to be able to reuse this template on another ComboBox using difference colors.
I was fairly certain the answer was no, but then again ControlTemplates are bound to the targeted type's properties at run time right? So then I thought as long as "Resources" is an accessible property of the ComboBox object at run time there should be a way. I verified by using snoop that "Resources" was indeed a readable property at run time:
Since Resources is just a dictionary, I then tried to set the background of the combobox dropdown to a resource defined exactly the same way as you did by adding these attributes to the "DropdownBorder" element in the default ComboBox control template:
Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Resources[ComboBoxHighlightBrush]}"
This will likely give you a compile time error saying that "Resources" is not accessible - but it's not an error that'll truly prevent you from building the solution and running it. At run time you'll see that it indeed works!
Despite it working, I'm not sure this is the best way to go about making your template reusable. Other than the annoying compile-time error, a consumer of the template will have to know about this magic string in order to add a correctly keyed brush to the resource dictionary.
I would consider the following:
A common way to lightly add to the behavior of an existing UI control is to used attached properties. You can bind to attached properties in ControlTemplates. This way you have something typed and not relying on a magic string.
Create a full on custom control. In my UI project I often have a need to have default text or a docked item at the top of my combobox drop down, so I created a custom control which has additional dependency properties that allows these features, but also allows me to modify colors that WPF's ComboBox doesn't let me out of the box.
Edit Based on your code, I think my interpretation of your question using ControlTemplates is correct.. if not the above won't make sense. If so, I also want to mention you should probably do bindings like
Foreground="{TemplateBinding Foreground}"
instead of
Foreground="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=Foreground}"
If anything just for easier reading. It's also a faster binding iirc.

Click handler of button in datatemplate not working

I have a resource dictionary combining a number of datatemplates. I'm including this resource dictionary as a ResourceDictionary.MergedDictionaries in my Page.Resources. One of my datatemplates is a ListView and while the item source and item click is working correctly, a separate button on the ListViewItem, set in the datatemplate, is not calling my click method. Im unsure about setting this up correctly.
This click method is defined in the code behind class the defines the pages Xaml including the resource dictionary and using my datatemplate for ListViewItems.
Dictionaries
DataTemplates.xaml <- ListView template here with a button click defined in the page cs, i.e. Click="MyPages_ClickMethod"
Pages
MyPage.xaml
MyPage.xaml.cs <- click method defined here, MyPages_ClickMethod()
Here is how I am setting up the button in the datatemplate:
<Button Tag="{Binding id}" Click="MultiShareSelectFileButton_Click" Background="Transparent" Visibility="{Binding multiShareSelected, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Inverted, Mode=OneWay}">
<Image Width="27" Source="ms-appx:///Assets/sharePlusIcon#2x.png" VerticalAlignment="Center" HorizontalAlignment="Center">
</Image>
</Button>
Is it possible to do this without using ICommand?
Something like: Click="{x:Bind Path=pages:ProductPage.MultiShareSelectFileButton_Click}", but this is complaining that MultiShareSelectFileButton_Click should be static
I'll get right to it. Here is the issue,
Your DataTemplate is in a resource dictionary. The resource dictionary is made for styles and converters if I may. Putting the DataTemplate in a resource dictionary is not recommended.
Why isn't it recommended?
The reason is straight, resource dictionaries are used to put global data. For ex: a control style that you might want to be available through out your app or your converters which are being used frequently.
This is because generally you would define the resource dictionary in your app.xaml which runs when your splashscreen appears.
Now if you have a lot of stuff (DataTemplates, Styles, Converters) all defined into resource dictionaries that are merged in <Application.ResourceDictionary> part of app.xaml, it's gonna have a significant impact on your app launch time, which will spoil your user's experience.
What's advised?
It's advised to keep your converters and styles not global unless you need them everywhere. For example: If you have a BoolToVisibilityConverter or a CustomRoundButtonStyle which you use only on one page/userControl out of 4. Then it doesn't make sense to load the style or converter for the other 3 Pages. So you should declare them in <Page.Resources> instead.
Same for your DataTemplate why declare it globally if you want to use it just once. Rather declare it to your <Page.Resources>. Your problem will be solved immediately as your Page will have a code-behind, so your xaml will know where to look for the method. That's where things are going wrong.
But in-case you have a single DataTemplate to be used on all your Views below is your solution:
Your Solution:
In-case you have to use it in a resource dictionary, use {x:Bind} and x:DataType="Models:YourDataContextModel" to bind your DataTemplate to your model. this ways your xaml will know exactly where to look for the method on click.
Below is a sample of it:
<DataTemplate x:Key="HelloTemplate" x:DataType="yourDefinedNameSpace:YourModel">
<Button Click="{x:Bind GoFetchData}"/>
</DataTemplate>
Where YourModel exists in a namespace defined as "yourDefinedNameSpace" in xaml and it contains a method of signature: internal void GoFetchData()
I hope this helps. Feel free to use the comments section if you have any doubts
I found that it was also necessary to specify ClickMode="Press" in Xaml.
<Button Content="" Focusable="True" FontFamily="Segoe UI Symbol" FontSize="16" Background="{StaticResource HeroLightGray}" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"
ClickMode="Press"
Command="{Binding DataContext.CopyMetadataSourceAsync, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" CommandParameter="{Binding .}" />
I do not recall having to do this in the past, but after spending hours troubleshooting, converting RelayCommand to IAsyncCommand, etc. this is the only thing that worked. In fact, I couldn't even get a regular code-behind "Click event" method to fire unless I included that ClickMode="Press"!

UserControls not rendering

Okay, to start, I'm pretty inexperienced with WPF and XAML, so any pointers or advice would be greatly appreciated.
I have a scheduling program that I'm working on that I need some help setting up. I had things working previously, but it wasn't organized correctly. I had UI elements in my ViewModels that I would add to a StackPanel at the initialization of the MainWindow. Generally not MVVM style coding. So I made some views (UserControls) to display the things I have, and most everything broke.
Basically, I have a Schedule ViewModel that has some parameters and a list of a different Room ViewModels. Each Room ViewModel has a RoomSchedule ViewModel that contains a list of RoomEvent ViewModels.
I'm trying to write controls for the things that need displaying. I've created a Schedule view, which has a list box of Room views, and the Room view uses the RoomEvent view to display the events of the room. The Room view uses the WPF Extended Toolkit's TimelinePanel, the rest of the controls are pretty much basic controls. The general idea has been: a model provides data to the ViewModel, which massages that data to what needs to be displayed. So an Event should know how to display itself, a Room should know how to display itself, and the Schedule should know how to display itself.
The problem I'm running into is: now that I've scooted everything from the xaml.cs or ViewModel files to their appropriate places, the controls aren't rendering at all. I've been reading other SO postings where people have the same problem, but none of them seem to work for beginner stuff like this. I think I'm close, it seems like all the controls are being created, and the DataContext's are being set correctly, but nothing is showing up.
This is, basically, what I have so far. I left some of the xaml boilerplate stuff off for succinctness:
Schedule.xaml:
<StackPanel>
<ListBox ItemsSource="{Binding Rooms}" >
<ListBox.ItemTemplate>
<DataTemplate>
<localcontrols:RoomView ScheduleStart="{Binding ElementName=ScheduleControl, Path=DataContext.Start}"
</DataTemplate>
<ListBox.ItemTemplate>
</ListBox>
</StackPanel>
RoomView.xaml:
<extended:TimelinePanel BeginDate="{Binding localcontrols:ScheduleStart}" EndDate="{Binding localcontrols:ScheduleEnd}"
<ItemsControl ItemsSource="{Binding Path=mRoomSchedule.mScheduledEvents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<localcontrols:EventView />
</DataTemplate>
</ItemsControls.ItemTemplate>
</ItemsControl>
</extended:TimelinePanel>
EventView.xaml:
<Border BorderThickness="1" BorderBrush="Black" extended:TimelinePanel.Date="{Binding mStartTime}" extended:TimelinePanel.DateEnd="{Binding mEndTime}">
<TextBlock Background="{Binding mColor}" Text="{Binding mEventID}" />
</Border>
The ScheduleStart and ScheduleEnd are dependency properties defined in RoomView.xaml.cs. My thinking was that Schedule would have Start and End properties that would be set in its constructor, and the RoomViews in the ListBox would bind to those properties to set the TimelinePanel's BeginDate and EndDate.
Maybe your bindings are wrong. When I need to bind to a dependency property I use the ElementName feature of binding to say which control I want and I give the root node a name, in this case Root. It's one way to solve it.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Weingartner.Controls"
x:Class="RoomView"
x:Name="Root">
<extended:TimelinePanel
BeginDate="{Binding ElementName=Root, Path=ScheduleStart}"
EndDate="{Binding ElementName=Root, Path=ScheduleEnd}"
>
<ItemsControl ItemsSource="{Binding Path=mRoomSchedule.mScheduledEvents}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<localcontrols:EventView />
</DataTemplate>
</ItemsControls.ItemTemplate>
</ItemsControl>
</extended:TimelinePanel>
</UserControl>

How to reflect inheritance of classes in WPF/XAML?

(First: I have some programming experience but am a beginner with XAML/WPF/StackOverflow. So please forgive anything "stupid" and ask if anything is not clearly described or you need additional info. Thanks.)
Introduction:
I have a base class Item with some properties (like Title, Notes, etc.).
From that I have some derived classes like ContactItem, MediaItem, etc. with additional properties which act as base classes for further specialized item types (e.g. ImageItem, MusicItem and VideoItem which are derived from MediaItem; Person and Institution are derived from ContactItem).
In WPF I want a page where multiple item types can be displayed together. I currently use an ItemsPanel for this and started to specify data templates for each item type - and there are many (more than 50).
Problem:
What I now want is some kind of "inheritance" of the item controls (e.g. have a "base" UserControl with a ContentPresenter and add additional controls for additional properties of derived classes/control templates).
What would be the best way to handle this in WPF/XAML without having to copy/paste the controls from the base classes for the derived item types in the data templates?
Any idea or hint in the right direction would be great.
If you need some code or additional info, please let me know.
You could simply nest each base class UserControl inside more derived UserControls, but you could find that this might soon become unmanageable. Another way would be to use the ContentControl element to display sections of larger DataTemplates from other DataTemplates like this:
<DataTemplate x:Key="NameTemplate" DataType="{x:Type ViewModels:UserViewModel}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:UserDerivedViewModel}">
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource NameTemplate}" />
<TextBlock Text="{Binding Age}" />
</DataTemplate>

How can I programmatically access elements defined in a ContentTemplate?

Let's say I've created a UserControl with the following ContentTemplate defined in XAML:
<UserControl.ContentTemplate>
<DataTemplate>
<Ellipse Name="myEllipse" Stroke="White"/>
<ContentPresenter Content="{TemplateBinding Content}"/>
</DataTemplate>
</UserControl.ContentTemplate>
How would I access the "myEllipse" element within my code so that, for example, I could find its height with "myEllipse.Height"? I cannot access it by name directly. I attempted to create a reference to it with:
Ellipse ellipse = ContentTemplate.FindName("myEllipse",this) as Ellipse;
It crashes when I run the program, saying it can't create an instance of my class. Perhaps I'm not using FindName correctly. If anyone can help me out it would be much appreciated.
Thanks,
Dalal
In order to use FindName on a DataTemplate, you will need a reference to the ContentPresenter. See Josh Smith's article How to use FindName with a ContentControl.
What you may actually want to do is to use a ControlTemplate rather than a DataTemplate. This should be easier to use and will let users of your control apply their own content templates or use implicit templates. If you do something like this:
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<ContentPresenter/>
<Ellipse Name="myEllipse" Stroke="White"/>
</Grid>
</ControlTemplate>
</UserControl.Template>
Then in code (perhaps in an OnApplyTemplate override) you will be able to do this:
var ellipse = Template.FindName("myEllipse", this) as Ellipse;
You should also decorate your class with a TemplatePartAttribute like this:
[TemplatePart(Name="myEllipse", Type = typeof(Ellipse))]
So that if anyone re-templates your control they know to provide an Ellipse element with that name. (This is less important if the class is only used internally.)
Finally, if all you want to do is change the color of the Ellipse, then you may just want to use data binding. You could create an EllipseColor dependency property on your control and just set Stroke="{TemplateBinding EllipseColor}".
Try
<Ellipse Name="myEllipse" Stroke="{TemplateBinding Background}"/>
instead of programmatically changing it.
There's a similar example here, with a blue filled ellipse.
http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

Categories