Binding ContentTemplateSelector to different Object then Content (ContentPresenter) - c#

Looking at my code I realized that many of my DataTemplateSelector based classes do pretty much the same thing, e.g. check a bool property of the bound object. I don't really want to have a bunch of objects that pretty much do the same, but I'm not entirely happy with my ideas.
The idea I like most is to bind the object I need to the Template and have my DataTemplateSelector have a property that can be set to the name of the e.g. boolean property that should be used to select the template (supplying the name when instantiating the selector in my xaml.
In the selector I would use reflection to access the property.
The second idea would be to just bind my boolean property, and then in my templates use a relative binding to the ancestor DataContext and work from there. I don't like this because is seems very counter intuitive and bad to maintain.
I could just implement an interface for this purpose (so that the boolean property would always have the same name), but this would mean that I would have some code in the model that is solely for the view. Or implement the interface in a ViewModel and be unable to have multiple DataTemplateSelector doing this in the same control (without splitting into classes just for this purpose).
Just bind by name. Very simple solution, but does not really work if you want to reuse your template in multiple controls. The solution I would go for if I e.g. had a single UserControl where I needed the template. Just make it a resource of this UserControl and have a simple and maintainable solution. As long as you don't want to use your templates in multiple controls, absolutely no problem there.
Are there any other ideas that I have overlooked? Any comments to the things listed above?
Example code for clarification
Beware: Since I'm not in my office, I typed the code without a compiler. Since it should illustrate this problem, compile errors etc. should not matter too much
stripped version of the model I'm talking about:
public class RegisterToRead : IValueNotifyPropertyChanged
{
...
public bool UseName { get {...} set {...} }
}
One of my typical TemplateSelectors
public class RegisterToReadTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
if(item != null && item is RegisterToRead)
{
RegisterToRead register = (RegisterToRead)item;
if( register.UseName)
return element.FindResource("nameSelectionTemplate") as DataTemplate;
else
return element.FindResource("manualEntryTemplate") as DataTemplate;
}
return null;
}
}
What I can't do, but in spirit want to
Have a TemplateSelector that looks like this:
public class BooleanTemplateSelector : DataTemplateSelector
{
public property DataTemplate TrueTemplate { get; set; }
public property DataTemplate FalseTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
if(item != null && item is bool)
{
bool value = (bool)item;
if( value)
return TrueTemplate;
else
return FalseTemplate;
}
return null;
}
}
And use it like this, to achieve to effect shown above:
<sel:BooleanTemplateSelector
TrueTemplate="{StaticResource nameSelectionTemplate}"
FalseTemplate="{StaticResource manualEntryTemplate}"
x:Key="RegisterToReadTemplateSelector" />
<ContentPresenter Content="{Binding SelectedRegister.UseName}"
ContentTemplateSelector={StaticResource ResourceKey=RegisterToReadTemplateSelector}"/>
But I can't do this, because then the DataContext inside the template will be set to the UseName Property, which is not what I would want.
This question has a duplicate on stackoverflow: Bind property for ContentTemplateSelector but pass DataContext to template where the OP did not get any answers, and went for the fourth idea. I wanted to restate the question to also get some feedback on the other ideas that I posted.

Related

"Binding" wpf Combox selectedValue to an integer?

I just started a new wpf project in hopes that I could learn a new technique as opposed to using winForms all the time.
I seem to be having way too much difficulty binding the selected value of a comboBox to an integer variable in my "MainWindow" class.
I have been looking at a host of "simple" examples from sites like codeproject, but they all seem way too complicated to just return the selected value of a comboBox. I am used to setting the "SelectedValueChanged" property and just setting a variable, which takes just a few clicks, like so:
public int value;
public void comboBox_SelectedValueChanged()
{
value = comboBox.SelectedValue();
}
Is there a similarly sweet, simple, and short way to properly "bind" the selected comboBox item to an integer?
I am trying to understand how to use INotifyPropertyChanged but I keep getting errors when I try to use it. This is what I have so far, but to be honest, I'm not sure where I am going with it:
// Combo Box Value
public class ComboValue
{
#region Members
int valueToReturn;
#endregion
# region Properties
public int numWeeks
{
get { return valueToReturn; }
}
#endregion
}
// Veiw Model Class
public class ComboValueViewModel:INotifyPropertyChanged
{
#region Construction
public ComboValueViewModel()
{
}
#endregion
}
and I've never used "#region" before, I have no clue what that is.
Could someone fill me in if I'm headed down the right path here?
You don't mention how much you know of MVVM but here goes. Your view will have an associated ViewModel class. In here you'll expose a property containing the items to bind to the combobox, e.g.:
public List<ComboValue> ComboItems { get; set; }
If you populate this property in the VM's constructor, then a List<> is probably sufficient; however you'll often see an ObservableCollection<> used for this kind of thing - this comes into its own if you need to add or remove items within your VM code - your view will react to such changes and update the list accordingly. This won't happen with a List<>.
As for INotifyPropertyChanged, I haven't implemented this pattern in the above code snippet. Again, it's not strictly necessary if you populate the collection in the VM constructor and won't be re-assigning that property again. However it's good practice to use the INPC pattern on your VM properties. Without it, if you were to reassign that property elsewhere in your code, e.g.:-
ComboItems = aNewListOfItems;
then the view wouldn't be made aware of the property change, and the ComboBox wouldn't update. If you need this to happen then implement the INPC pattern on the property, e.g.:-
public List<ComboValue> ComboItems // or ObservableCollection<ComboValue>
{
get
{
return _comboItems;
}
set
{
if (_comboItems != value)
{
_comboItems = value;
OnPropertyChanged("ComboItems");
}
}
}
As you are working with a ComboBox, your VM should also expose a property that you bind to the control's SelectedItem property. This property should implement INPC, e.g.:-
public ComboValue SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
As you select items in the combo, the VM's SelectedItem property will change to reflect the current selection.
Finally, your XAML should end up looking something like this:-
<ComboBox ItemsSource="{Binding ComboItems}" SelectedItem="{Binding SelectedItem}" />
Hope this gives you a little "primer" into WPF binding! (Code snippets taken from memory so may not be 100% correct!).
Edit
Your ComboValue class exposes a numWeeks property. As it stands, you'll probably find that your ComboBox displays a list of ComboValue type names. To get the number to appear, the easiest thing is just to override .ToString() in your class and return the value of numWeeks. For more advanced formatting of items in controls such as this, you'll typically specify an ItemTemplate (again, plenty of examples can be found via Google!).

Set DataTemplate in template selector as dynamic resource

I have a control in which I need to set data template based on various conditions so I decided to use a DataTemplateSelector which selects templates from resources of the control its being assigned to.
This works, but here is a catch: I am reloading these resources from file (when there is file system change) and I need to update already rendered controls with the new template. This would work if I simply used DynamicResource instead of selector.
Selector looks something like this:
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
//complex rules that select the template are here
//this unfortunately sets the template statically - if it changes, it won't get updated
return template;
}
So if the resources change, the selector is never reevaluated as it would be if I used DynamicResource.
I had an idea to solve this: select the template in ViewModel, so that when resources change, I can update my DataTemplate property.
My attempt of ViewModel (simplified example, it implements INotifyPropertyChange properly):
class MyViewModel {
public DataTemplate DataTemplate {get;set;}
public MyModel Model {
get {return _model;}
set {
if(_model != value) {
_model = value;
//Select new template here
//DUH: how do I access the resources as I would in DataTemplateSelector, when I don't have access to the container parameter?
}
}
}
}
I am pretty sure that I am doing this the wrong way, but how to do it properly? I don't want to access the resources from some hard-coded static location for various reasons. I really need to find them in the container it is being assigned to.
I know the question is confusing, so feel free to ask and I will try to clarify.
So after long hours of trying to figure this out using various hackish methods, it showed to be really easily solvable problem.
We set our data template (only key to data template in fact) in view-model and then apply the template in simple attached property.
xaml:
<ContentControl Content="{Binding Content}" local:ContentTemplate.ContentTemplateKey="{Binding TemplateKey}">
<!-- Some other stuff -->
</ContentControl>
attached property:
public static class ContentTemplate
{
public static object GetContentTemplateKey(DependencyObject obj)
{
return (object)obj.GetValue(ContentTemplateKeyProperty);
}
public static void SetContentTemplateKey(DependencyObject obj, object value)
{
obj.SetValue(ContentTemplateKeyProperty, value);
}
public static readonly DependencyProperty ContentTemplateKeyProperty = DependencyProperty.RegisterAttached("ContentTemplateKey", typeof(object), typeof(ContentTemplate), new UIPropertyMetadata(null, OnContentTemplateKeyChanged));
private static void OnContentTemplateKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var key = e.NewValue;
var element = d as FrameworkElement;
if (element == null)
return;
element.SetResourceReference(ContentControl.ContentTemplateProperty, key);
}
}
binding object if resource uses x:Key="ResourceName":
new
{
Content = something,
TemplateKey = "ResourceName",
}
binding object if resource uses TargetType="{x:Type Person}":
new
{
Content = something,
TemplateKey = new DataTemplateKey(typeof(Person)),
}
Of course the binding object should implement INotifyPropertyChange so the templates update on the fly.

Grouped GridView. C#. Windows 8

I've done a lot of samples but nowhere encountered on my problem.
Namely, I would like to create a Grouped GridView, which consists of two groups, with the exception that each group is made up of completely different collection. For example, I would like to in the first group were Animals, and in the second, Cars.
I would also like to each of these groups had a different Template ;)
Make both your inner collection items derive from a common base class. When I did this I had an ItemBase class, and my Event, Story, and Party classes all derived from Item base.
Then, my groups collection items each contained a definition for Items of ObservableCollection. (I guess, thinking about it now, I could have used object as the implied base type, but I didn't) When coded this was actually populated with my derived classes, e.g.
Items.Add(new Event { Title = "I am an event" };
When you display the items in your grid, you will want to create a new class that derives from ItemTemplateSelector, and override the SelectTemplateCore(object item, DependencyObject container) method. My logic was as simple as
if(item is Event) { return EventTemplate; }
else if(item is Story) { return StoryTemplate }
else { return DefaultTemplate; }
(My Party item used the default template.)
Create a ObservableCollection and push your collection items.
Like This:
public class ScreenGroupModel
{
private ObservableCollection<object> _groupItems = new ObservableCollection<object>();
public ObservableCollection<object> GroupItems
{
get { return this._groupItems; }
}
public ScreenGroupModel()
{
}
public ObservableCollection<object> GetScreenGroups()
{
_groupItems.Add(new Class1);
_groupItems.Add(new Class2);
return _groupItems;
}
}
This Sample, simple collection showing. You can be used DataTemplateSelectors. Every kind of class, select a template.
ObservableCollection -> "object" type is important. Because, object is base type. You can be add, every kind class.
Regards ;)

Get type of object and dynamically set it as the object

Is it possible to get the type of object and dynamically set it as the object. I have several ViewModels that all contain the same Property and want to do something like this
if (this.DataContext is CamerasViewModel)
{
//Type type = Type.GetType((this.DataContext.ToString());
object o = Assembly.GetExecutingAssembly().CreateInstance(this.DataContext.GetType().ToString());
Type type = o.GetType();
foreach (ButtonViewModel button in (this.DataContext as type).Buttons)
{
if (button.DisplayName == this.Content.ToString())
{
this.Template = (ControlTemplate)this.FindResource(button.TemplateResource.Substring(0, button.TemplateResource.Length - 3) + "pr");
break;
}
}
}
Instead of saying this.DataContext as CamerasViewModel I want to say this.DataContext as THEDYNAMICTYPE
Any suggestions?
You're probably much better taking all of your classes that have a Buttons property that you want to update the template on, and having them implement an interface. The interface would look something like this:
public interface IHasButtons
{
public IEnumerable<ButtonViewModel> Buttons {get; set;}
}
and your view models would be declared something like this:
public class CamerasViewModel : IHasButtons
{
public IEnumerable<ButtonViewModel> Buttons {get {. . .} set {. . .} }
. . .
}
Then, in your if statement, instead of checking if the object is a CamerasViewModel, check if it's an IHasButtons. Doing it this way is a lot safer than trying to determine at runtime if there's a Buttons property on the object. You could get unlucky and run across a Buttons property that has the same name, but different functionality than you're expecting. Then you're back writing crazy logic to determine if this is REALLY the Buttons collection that you're looking for. If you do it with interfaces, it's extremely clear. If a ViewModel implements IHasButtons, then it's a ViewModel you want to update. If it doesn't implement IHasButtons, you'll skip right over it

Update binding without DependencyProperty

I have a lot of existing business objects with many properties and collections inside which I want to bind the userinterface to. Using DependencyProperty or ObservableCollections inside these objects is not an option. As I know exactly when I modify these objects, I would like to have a mechanism to update all UI controls when I do this. As an extra I also don't know which UI controls bind to these objects and to what properties.
Here is a simplified code of what I tried to do by now:
public class Artikel
{
public int MyProperty {get;set;}
}
public partial class MainWindow : Window
{
public Artikel artikel
{
get { return (Artikel)GetValue(artikelProperty); }
set { SetValue(artikelProperty, value); }
}
public static readonly DependencyProperty artikelProperty =
DependencyProperty.Register("artikel", typeof(Artikel), typeof(MainWindow), new UIPropertyMetadata(new Artikel()));
public MainWindow()
{
InitializeComponent();
test.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
artikel.MyProperty += 1;
// What can I do at this point to update all bindings?
// What I know at this point is that control test or some of it's
// child controls bind to some property of artikel.
}
}
<Grid Name="test">
<TextBlock Text="{Binding Path=artikel.MyProperty}" />
</Grid>
This is, I tried to pack my object into a DependencyProperty and tried to call UpdateTarget on this, but didn't succeed.
What could I do to update the corresponding UI controls?
I hope I described my situation good enough.
Using INotifyPropertyChanged is a good alternative to DependencyProperties.
If you implement the interface you can raise the PropertyChanged event with null as parameter to notify the UI that all properties changed.
(I'm going to assume you can't add INotifyPropertyChanged to your business objects either, and that you don't want to add another "view of the data model" layer of wrapper objects a la MVVM.)
You can manually update bound properties from their data source by calling BindingExpression.UpdateTarget().
myTextBlock.GetBindingExpression(TextBlock.TextProperty).UpdateTarget();
To update all bindings on a control or window, you could use something like this:
using System.Windows.Media;
...
static void UpdateBindings(this DependencyObject obj)
{
for (var i=0; i<VisualTreeHelper.GetChildrenCount(obj); ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is TextBox)
{
var expression = (child as TextBox).GetBindingExpression(TextBox.TextProperty);
if (expression != null)
{
expression.UpdateTarget();
}
}
else if (...) { ... }
UpdateBindings(child);
}
}
If you're binding a diverse set of properties then rather than handling them individually as above, you could combine the above with this approach to enumerate all dependency properties on a control and then get any BindingExpression from each; but that relies on reflection which will not be particularly performant.
As a footnote, you can also use BindingExpression.UpdateSource() if you want to explicitly write back to the data source. Controls usually do this anyway when their value changes or when they lose focus, but you control this and do it by hand with {Binding Foo, UpdateSourceTrigger=Explicit}.
As I know exactly when I modify these objects, I would like to have a mechanism to update all UI controls when I do this.
You will find that the most straightforward and maintainable way to deal with this is to implement view model classes for each class you want to present in the UI. This is probably true if you can modify the underlying classes, and almost certainly true if you can't.
You don't need to be using dependency properties for this. Dependency properties are only necessary on the targets of binding, which is to say the controls in the UI. Your view model objects are the source; they need only implement INotifyPropertyChanged.
Yes, this means that you will need to build classes that contain a property for each property exposed in the UI, and that those classes will need to contain observable collections of child view models, and you'll have to instantiate and populate those classes and their collections at runtime.
This is generally not as big a deal as it sounds, and it may be even less of one in your case. The traditional way to build a view model that's bound to a data model is to build properties like this:
public string Foo
{
get { return _Model.Foo; }
set
{
if (value != _Model.Foo)
{
_Model.Foo = value;
OnPropertyChanged("Foo");
}
}
}
But if, as you've claimed, you know when the objects are being updated, and you just want to push the updates out to the UI, you can implement read-only properties, and when the underlying data model gets updated make the view model raise PropertyChanged with the PropertyName property of the event args set to null, which tells binding, "Every property on this object has changed; update all binding targets."

Categories

Resources