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.
Related
I need help trying to understand why this is not working. According to MSDN, TemplateBinding is what should be used when binding the property of a control in a template to a property of the control implementing the template.
Except that Template Binding is not two-way. For two-way you need to use binding and then specify the relative source as TemplatedParent.
So I have the following XAML:
template
<ItemContainerTemplate x:Key="colHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
<ToggleButton Style="{StaticResource ToggleButtonStyle}" IsChecked="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay, Path=(props:VisibilityHelper.IsGroupCollapsed)}"/>
</StackPanel>
</ItemContainerTemplate>
which is used here
<dxg:GridColumn x:Name="Total" Header="Total" FieldName="field1" Width="Auto" HorizontalHeaderContentAlignment="Center" props:VisibilityHelper.IsGroupCollapsed="False" HeaderTemplate="{StaticResource colHeaderTemplate}">
<dxg:GridColumn.EditSettings>
<dx:TextEditSettings HorizontalContentAlignment="Center"/>
</dxg:GridColumn.EditSettings>
</dxg:GridColumn>
The toggle button in the template must set a dependency property on the grid column. This works fine when the template is binding to a parent ie. the controls are nested,
I just can't figure out what I am doing wrong.
MSDN ref - http://msdn.microsoft.com/en-us/library/ms742882.aspx
One of the many SO posts about this - In WPF, why doesn't TemplateBinding work where Binding does?
Thank you
Right so I have found the solution. Firstly DataTemplate does work. As #Quercus, it is all in the binding to the correct control.
In my case not the GridColumn but the GridColumnHeader. So this
IsChecked="{Binding RelativeSource={RelativeSource AncestorType=dxg:GridColumnHeader}, Path=DataContext.(props:VisibilityHelper.IsGroupCollapsed)}"
works perfectly...when bound to the correct parent.
Also as #Quercus stated, the template is actually nested and that is why this works. I used a tool called Snoop which actually shows you the visual tree of the application and then the datacontext of the selected element. Using this I solved this issue as well as 2 others I was having.
I really hope this helps someone somewhere before everyone goes to MAUI or WinUI 3.
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"!
In my WPF app we are using an adorner for displaying validation messages, in the particular case there is a single row grid that has multiple controls some of which have validation. The problem I'm having is that I want to force the width of the error message control to be the same as the grid but can't seem to find a way to reference that grid from the adorner template. Here is a sample of what I tried:
<ControlTemplate x:Key="Local_TopAdornedTemplateWide">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid}, ElementName=adornedElement}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
This causes the application to crash with an XamlParseException.
Ideally the solution would not be specific to a grid so that it would get the width of any container type, but for now grid is the only use case.
Edit:
Here is an example of another template we use in the application; this template would not work for my case as it would limit the error to be the width of a single column of the aforementioned grid:
<ControlTemplate x:Key="Local_TopAdornedErrorTemplate">
<StackPanel>
<AdornedElementPlaceholder x:Name="adornedElement"/>
<TextBlock MaxWidth="{Binding ElementName=adornedElement, Path=ActualWidth}"
TextWrapping="Wrap"
Text="{Binding Converter={StaticResource Local_ValidationErrorMessageConverter}}"
Style="{DynamicResource Error_Text}"
Padding="2 1 0 0"
Visibility="{Binding ElementName=adornedElement, Mode=OneWay, Path=AdornedElement.IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
</StackPanel>
</ControlTemplate>
Using snoop I captured the following two screenshots (I could not take one of the full stack to prevent posting anything proprietary)
This shot shows the grid I mentioned previously, within this it is the FinancialTextBox item that is being adorned
This shot shows two things, the item selected in blue is the highest ancestor of the grid in the previous shot, the yellow highlight is the Textbox from the content template
With those two it seems to be apparent that (based on information from Contango's answer) the two items aren't not in the same visual tree which would lead me to believe my question is not possible. However the second template I added (which does work) points that at least some visual information from the adorned element lives on in the place holder.
So now my question boils down to a) does this information include the parent of the adorned element and b) how can this be accessed via a binding on a different element?
This ended up being a lot simpler than the path I was trying to go down.
I was doing some reading on the AdornedElementPlaceholder class and came across this entry on MSDN and noticed that the class actually has a property called parent, with that I tried the following binding and it works perfectly:
MaxWidth="{Binding ElementName=adornedElement,
Mode=OneWay,
Path=AdornedElement.Parent.ActualWidth}"
WPF is quite powerful and flexible.
You can bind any property in any XAML tag to any property in any other XAML tag.
For example, you could write a test app that binds the Text property of an input box to the Text property of a label, so as you type something into the text box, the label would change automatically (assuming you use UpdateSourceTrigger=PropertyChanged). This is a direct XAML to XAML binding, with no C# in sight.
Similarly, you could bind the width of your error box to the width of the parent control, whatever that may be.
Google RelativeSource and AncestorType, this is a great link:
http://druss.co/2013/10/wpf-binding-examples/
See if you can grok how the Visual Tree and Logical Tree works in WPF, once you understand that, you will understand more of how binding works.
I'd also recommend using the free tool Snoop to look at the Visual Tree. XAML Spy is excellent, but not free.
Snoop can tell you if there is anything that has a bad binding at runtime (you set the filters up, and it will list all bad bindings).
You can use Snoop to get the full XAML path of your source (the XAML you wrote above), then get the full XAML path of the target (i.e. the ActualWidth of your Grid), then compare them: it may be quickly apparent that one is not the ancestor of the other, as they are on different branches of the visual tree, or that there is some other issue which is preventing a simple walk up the visual tree from working.
If you just want to get something working, as a proof of concept, try naming the target XAML grid using x:Name, and reference it by name instead of AncestorType.
This may well have been asked and answered before but I really wasn't sure how to phrase the question.
I have a dictionary (MyLookup) and I want a control to bind to a particular key and value in the dictionary. The key for the dictionary is a string, and lets say the particular item is 'MyItem'. The output should be something like:
MyItem value: 43
One idea was to use a couple of text blocks and a tack panel, one for the key and one for the value. I tried to encapsulate the item of interest in the stack panel, but things get fruity when defining the binding path for the second text block
<StackPanel Orientation="Horizontal" Tag="MyItem">
<TextBlock Text ="{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}, StringFormat={0} value:}"/>
<TextBlock Text ="{Binding Path=MyLookup[{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}]}"/>
</StackPanel>
It might also be possible to do it with a single text block and some string formatting?
<TextBlock Text="{Binding Path=MyLookup[MyItem], StringFormat={0} value: {1}}"/>
Bleugh, it all falls apart there as there need to be 2 outputs?! My current thinking is to use a multi-converter to take the string and convert it to a string and an int, but I was wondering if thee were cunning things that I'm missing.
Once again, A Binding can only be set on a DependencyProperty of a DependencyObject. You're trying to use a Binding for the integer that specifies which key/value to look at from your Dictionary, but you can't because it is an integer and not a DependencyProperty.
Instead of doing this, you should create a view model that contains all of the data and functionality that your view requires. As #HighCore mentioned, once you have organised your data in the right way, then all of these nasty problems will disappear.
Hmm, what the h**k is this {Binding Path=MyLookup[{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}]}
Take a look at ObjectDataProvider.
That bad ass allows you do craziest Bindings you can think of and all that is happening in XAML.
http://msdn.microsoft.com/en-us/library/system.windows.data.objectdataprovider%28v=vs.110%29.aspx
We have a WPF application with a standard MVVM pattern, leveraging Cinch (and therefore MefedMVVM) for View -> ViewModel resolution. This works well, and I can bind the relevant controls to properties on the ViewModel.
Within a particular View, we have an Infragistics XamGrid. This grid is bound to an ObservableCollection on the ViewModel, and displays the appropriate rows. However, I then have a specific column on this grid which I am trying to bind a TextBox text value to a property on the parent DataContext, rather than the ObservableCollection. This binding is failing.
We've gone through several options here including:
Using AncestorType to track up the tree and bind to the DataContext of the parent UserControl like so (from the great answer to this question, as well as this one)...
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
Specifying the ElementName and trying to target the top level control directly. Have a look here if you'd like to read about using ElementName.
Using a 'proxy' FrameorkElement defined in the resources for the UserControl to try and 'pass in' the context as required. We define the element as below, then reference as a static resource...
<FrameworkElement x:Key="ProxyContext" DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"></FrameworkElement>
In this case the binding finds the FrameworkElement, but can not access anything beyond that (when specifying a Path).
Having read around, it looks quite likely that this is caused by the Infragistics XamGrid building columns outside of the tree. However, even if this is the case, at least options 2 or 3 should work.
Our last thoughts are that it is related to the V - VM binding, but even using Snoop we've yet to find what the exact issue is. I'm by no means an expert with WPF binding so any pointers would be appreciated.
EDIT: I have found some templating examples from Infragistics here that I will try.
EDIT 2: As pointed out by #Dtex, templates are the way to go. Here is the relevant snippet for use with a XamGrid:
<ig:GroupColumn Key="CurrentDate">
<ig:GroupColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.CurrentDateTest, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</DataTemplate>
</ig:GroupColumn.HeaderTemplate>
<ig:GroupColumn.Columns>
I've left the XML open... you'd simply add the columns you wanted, then close off the relevant tags.
I dont know about XamGrid but that's what i'll do with a standard wpf DataGrid:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding DataContext.MyProperty, RelativeSource={RelativeSource AncestorType=MyUserControl}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Since the TextBlock and the TextBox specified in the cell templates will be part of the visual tree, you can walk up and find whatever control you need.
Because of things like this, as a general rule of thumb, I try to avoid as much XAML "trickery" as possible and keep the XAML as dumb and simple as possible and do the rest in the ViewModel (or attached properties or IValueConverters etc. if really necessary).
If possible I would give the ViewModel of the current DataContext a reference (i.e. property) to the relevant parent ViewModel
public class ThisViewModel : ViewModelBase
{
TypeOfAncestorViewModel Parent { get; set; }
}
and bind against that directly instead.
<TextBox Text="{Binding Parent}" />