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
Related
Let me preface this by saying, I'm not sure if this is even possible. I've got two similar collections of complex objects that I'm using as source data for two different Items controls. Well, let's call the first collection the old data, and let's call the second collection the new data.
Like I said above, I've got two items controls - one binding to old, and the other binding to new. Well, in the item template of the new items control, I want to be able to bind to the old data, however I need to be able to use an index because the property is List of items. Maybe code snippets will help explain what I'm after:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Textblock Text="{Binding Path=OldSourceData.MyList[0]}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
However, I need to replace 0 with the alternation index of the current item. I'm using this to get the correct index {Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource TemplatedParent}}
How do I replace 0 with the alternation index?
EDIT
I'm trying to figure out how to do something like this: <Textblock Text="{Binding Path=OldSourceData.MyList[{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource TemplatedParent}}]}"/> Obviously, this syntax doesn't work, however is it possible to accomplish this somehow?
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.
<press_limits value="1055" label="Press Limits" type="single 317" format="object">
<projected_area value="0.052944637336319995" label="Projected area of part" type="real(m*m)"/>
<press_tonnage value="500.0" label="Press tonnage" type="real(g)" units="0Ton"/>
<within_press_limit value="1" label="within limits of press" type="boolean"/>
From XML like the above, the XAML below will generate a form that displays the values, with appropriate controls and value formats. But I can't get the stuff converted back. I fail to see a simple change that will meet the requirements of Binding. Perhaps fixing this requires an architecture change. How can I do this differently?
The magic starts here. Bind this ItemsControl to an XmlElement, and it builds a ControlChooser for each subelement.
<ItemsControl ItemsSource="{Binding XPath=*}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<W3V:ControlChooser Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ControlChooser triggers on the format attribute:
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#format}" Value="spin">
<Setter Property="ContentTemplate" Value="{StaticResource combo}" />
</DataTrigger>
to pick a DataTemplate:
<DataTemplate x:Key="combo" > <W3V:ComboView /> </DataTemplate>
which instatiates the following control:
<ComboBox Style="{StaticResource ComboButtonStyle}" Width="200"
Text="{Binding Path=., ## PROBLEM, BUT HOW TO AVOID?
Converter={StaticResource valueFormattingConverter },
IsEditable="True" />
valueFormattingConverter uses the #units, #type, and #value attributes to produce properly formatted text. The trouble is, this doesn't convert back. I asked here: TextBox ConvertBack event doesn't fire for XML element, and learned that it is impossible for Path=. to be used as '.' is an object but not a dependency property.
So then, I need a way to provide a DependencyProperty for Binding. So ComboView needs to receive an object that has a property which is or has the XmlElement I want. I think this means I really need to change things around but haven't the slightest idea how. Maybe there's an MVVM approach to doing this??? Any insights will be appreciated.
An upcoming issue is a need to validate the data typed in and process the information.
The MVVM method: pull the data out of the XML file, and put it a class which is the ViewModel. Each property in the ViewModel corresponds to an item of data in the XML file. Then use a custom DataTemplate to render the contents of the ViewModel to the screen.
We can extend this to render a list of items. Each item in the list is a ViewModel. A DataTemplate always renders based on the type of the property it is attached to. So you can have a list of objects, and a custom DataTemplate for each item in the list. Of course, all of the items in the list would have to inherit from the same type.
This means you can have a list of items, and each item can render differently depending on the type of data in the XML file. This means that each item in the list can have a custom look and feel with different number of decimal places, colors, etc.
I've used this technique before, and it works well.
Update
For examples, see:
http://www.wpftutorial.net/datatemplates.html
http://www.stackoverflow.com/questions/3400532/display-multiple-types-from-a-single-list-in-a-wpf-listbox.
If I was solving this, I would look at generating C# on the fly. Here is how I would do it:
All XAML compiles to a series of C# commands.
I would set the XAML to format things the way I like it.
I would find the equivalent C# code.
I would then insert the appropriate if/else statements to alter the C# to suit.
Another method that I would try:
Its possible to render custom XAML into an area on the screen.
I would edit the XAML, based on the XML, then display this custom XAML on the screen.
I'd be curious to know if either of these methods work in practice, or if there is a better method that would work.
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}" />
I have some code that looks like this:
<Expander Header="{Binding SelectedSlot.Name}"
Visibility="{Binding ShowGroupSlot, Converter={StaticResource BooleanToVisibility}}">
<Controls:GroupPrototypeSlotControl Slot="{Binding DataContext.SelectedSlot,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Expander}}}" />
</Expander>
This works, but the ugliness of the Slot Binding bothers me. This is required because the GroupPrototypeSlotControl has a GroupPrototypeViewModel as its DataContext. If I simply use {Binding SelectedSlot}, it attempts to resolve it on the 'child' ViewModel, which fails. I get around this by explicitly looking at the DataContext of my parent control. Is there a cleaner way to do this type of binding?
EDIT: I found a cleaner way of resolving my problem, though it still feels like a hack. I modified the GroupPrototypeSlotControl so that it has a top-level LayoutRoot (a StackPanel, in this case) and then set the DataContext of LayoutRoot to the ViewModel rather than setting the DataContext of the entire control. This allows me to use the {Binding SelectedSlot} syntax where I use the control (since the control still has the parent DataContext), at the cost of slightly increasing the complexity of the control. In general, this is probably the better pattern for a custom control, since the consumer of the control expects a {Binding} to resolve to their parent DataContext if one isn't explicitly specified.
A slightly cleaner (shorter) way is to use ElementName in your Binding like this:
<Expander Header="{Binding SelectedSlot.Name}"
x:Name="expander"
Visibility="{Binding ShowGroupSlot, Converter={StaticResource BooleanToVisibility}}">
<Controls:GroupPrototypeSlotControl Slot="{Binding DataContext.SelectedSlot, ElementName=expander}" />
</Expander>