Using controls in a LongListSelector - c#

I'm using a LongListSelector to display a list of complex objects and update the datatemplate (a control) depending on the number of items in the bound objects list property.
I have tried the following.
Accessing the data in the OnItemRealized event to try and get the bound control and update it via a method call.
Not sure if possible
Adding a property to the control being bound which adds controls to a wrap panel when the property is set.
The set accessor in the controls property is never hit.
Hopefully it's clear what im trying to achieve.
Is it possible to call functionality in a property being bound to as shown in my control
If not is it possible to access the control being bound and call exposed functionality that way
If anyone could shed any light on my issue i would greatly appreciate it!
Data Template
<DataTemplate x:Key="LLS_SomeTemplate" >
<MyApp:ObjectTemplate SomeObjects="{Binding SomeEntities}"/>
</DataTemplate>
Object being bound
public class SomeObject
{
public ObservableCollection<Entities> _SomeEntities { get; set; }
public ObservableCollection<Entities> SomeEntities
{
get
{
if (_SomeEntities == null)
_SomeEntities = new ObservableCollection<Entities>();
return _SomeEntities;
}
set
{
_SomeEntities = value;
}
}
public SomeObject()
{
}
}
Control Property
public static DependencyProperty SomeObjectsProperty = DependencyProperty.Register("SomeObjects", typeof(ObservableCollection<Entities>), typeof(ObjectTemplate), new PropertyMetadata(new ObservableCollection<Entities>()));
public ObservableCollection<SomeObject> SomeObjects
{
get
{
return (ObservableCollection<SomeObject>)GetValue(SomeObjectsProperty);
}
set
{
SetValue(SomeObjectsProperty, value);
if (value != null && value.Count > 0)
{
foreach (SomeObject eLink in value)
{
//Add a new control to a wrap panel for each object in the list
}
}
}
}

There are few ways how CLR set up dependency properties. You must avoid perform operations in setter. Create value changed event handler instead:
public static DependencyProperty SomeObjectsProperty = DependencyProperty.Register("SomeObjects", typeof(ObservableCollection<Entities>), typeof(ObjectTemplate), new PropertyMetadata(new ObservableCollection<Entities>(), new PropertyChangedCallback(OnSomeObjectsPropertyChanged));
private static void OnSomeObjectsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as ObjectTemplate).UpdateSomeObjects(e.NewValue as SomeObjects);
}
public void UpdateSomeObjects(SomeObjects value)
{
if (value != null && value.Count > 0)
{
foreach (SomeObject eLink in value)
{
//Add a new control to a wrap panel for each object in the list
}
}
}
Hope it helps you to solve your problem

Related

Databindings for custom control wont work?

I am having trouble understanding why my databindings do not seem to work with my custom class. I made (hacked) my class extend the Control class to add the databindings functionality but it doesn't actually bind to my custom property.
My code for my custom class is:
public class RadioButtonSet : System.Windows.Forms.Control
{
private Dictionary<System.Windows.Forms.RadioButton, int> buttonList;
private int selectedValue;
public RadioButtonSet()
{
buttonList = new Dictionary<System.Windows.Forms.RadioButton, int>();
}
public void AddButton(System.Windows.Forms.RadioButton button, int buttonValue)
{
if (this.buttonList.ContainsKey(button))
throw new Exception("Button set already contains specified button");
else if (buttonValue <= 0)
throw new Exception("Cannot add specified key to button set");
else if (button == null)
throw new Exception("Parameter button cannot be null");
else
{
button.CheckedChanged += button_CheckedChanged;
this.buttonList.Add(button, buttonValue);
}
}
private void setSelectedButton()
{
this.buttonList.FirstOrDefault(x => x.Value == this.selectedValue).Key.Checked = true;
}
private void button_CheckedChanged(object sender, EventArgs e)
{
System.Windows.Forms.RadioButton btn = sender as System.Windows.Forms.RadioButton;
this.selectedValue = this.buttonList[btn];
}
public int SelectedButton
{
get
{
return selectedValue;
}
set
{
selectedValue = value;
setSelectedButton();
}
}
}
And I try to bind to this class using the following, where rbs_admin is an instance of my custom class:
rbs_admin.DataBindings.Add("SelectedButton", datatable, "admin");
I do not know what information may help so here goes.
I get the information to bind from a datatable which is populated by a data adapter. This custom class is not in it's own file, its part of another static class in my project.
I just dont understand as I created a custom textbox with the same custom property and it binds and works fine.
Any help is much appreciated.
Im talking about something like this:
someListControl.DataSource = datatable;
someListControl.DisplayMember = "someAnotherColumnName"
rbs_admin.DataBindings.Add("SelectedButton", datatable, "admin");
Then, selecting an item from list control will cause your control to update its binding according to the selected item.

Changing standard property into a DependencyProperty

In developing some UserControls for internal use I followed this exmaple from MSDN http://msdn.microsoft.com/en-us/library/vstudio/ee712573(v=vs.100).aspx
The public value of one control is used by another control. The way I have this working currently is hooking into an event that is fired in the first control through code-behind. I am thinking that making one or both of the properties DependencyProperties which would eliminate the need for the code-behind.
public partial class UserControl1 : UserControl
{
private DataModel1 dm;
public UserControl1()
{
this.DataContext = new DataModel1();
dm = (DataModel1)DataContext;
InitializeComponent();
}
public DataValue CurrentValue
{
get { return dm.CurrentValue; }
set { dm.CurrentValue = value; }
}
}
public class DataModel1 : INotifyPropertyChanged
{
private DataValue _myData = new DataValue();
public DataValue CurrentValue
{
get { return _myData; }
set { if (_myData != value) {_myData = value OnPropertyChanged("CurrentValue"); }
}
// INotifyPropertyChanged Section....
}
The property is just a pass through from the DataModel1 class.
Both UserControls are very similar in their structure and have the same public properties. I would like to replace the code behind eventhandler with a Binding similar, I think to:
<my:UserControl1 Name="UserControl1" />
<my:UserControl2 CurrentValue={Binding ElementName="UserControl1", Path="CurrentValue"} />
but the standard examples of DependencyProperties have getters and setter that use the GetValue and SetValue functions which use a generated backing object instead of allowing a pass through.
public DataValue CurrentValue
{
get { return (DataValue)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
I think the DP should look like:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1));
How can I change the definition of the public backing property to support the databinding pass through?
I found that jumping into the OnPropertyChanged event allowed me to pass the data through to the DataModel1. I am not 100% sure that this is the correct answer but it gets the job done.
Here is the corrected code:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1),
new PropertyMetadata(new PropertyChangedCallback(OnCurrenValueChanged)));
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
if (e.NewValue != null)
{
uc.dm.CurrentValue = e.NewValue as DataValue;
}
}
public DataValue CurrentValue
{
get { return GetValue(CurrentValueProperty) as DataValue; }
set { SetValue(CurrentValueProperty, value); }
}

A custom attribute for setting whether a textbox is editable

I am trying to setup a custom attribute in C# to set whether a business object property is editable, eventually enabling or disabling ReadOnly a textbox in XAML. Since (I thought) IsEditable was already implemented in System.Windows.Controls, i thought this would work:
[AttributeUsage(AttributeTargets.Property)]
public class EditableAttribute : Attribute
{
public EditableAttribute(bool isEditable)
{
this.ReadOnly = !isEditable;
}
public virtual bool ReadOnly { get; set; }
}
Well, go figure, it doesn't. I set [Editable(false)] to a string in an object and it is still editable. I have a feeling I'm not even close. Any help or suggestions would be greatly appreciated!
I am aware this can be setup as a style in xaml, but for this case it needs to be in the business object.
Thanks
You can use BindingDecoratorBase to use custom binding and use an attribute.
The following code is just me modifying my code in my project that uses custom validation . It probably should be refractored.
public interface IEditatble
{
void SetValue(Control sender, DependencyProperty property);
}
[AttributeUsage(AttributeTargets.Property)]
public class EditableAttribute : Attribute, IEditatble
{
public EditableAttribute(bool isEditable)
{
this.ReadOnly = !isEditable;
}
public virtual bool ReadOnly { get; set; }
public void SetValue(System.Windows.Controls.Control sender, System.Windows.DependencyProperty property)
{
sender.SetValue(property, this.ReadOnly);
}
}
You can create a custom binding:
public class ReadonlyBinding : BindingDecoratorBase
{
private DependencyProperty _targetProperty = null;
public ReadonlyBinding()
: base()
{
Binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
}
public override object ProvideValue(IServiceProvider provider)
{
// Get the binding expression
object bindingExpression = base.ProvideValue(provider);
// Bound items
DependencyObject targetObject;
// Try to get the bound items
if (TryGetTargetItems(provider, out targetObject, out _targetProperty))
{
if (targetObject is FrameworkElement)
{
// Get the element and implement datacontext changes
FrameworkElement element = targetObject as FrameworkElement;
element.DataContextChanged += new DependencyPropertyChangedEventHandler(element_DataContextChanged);
}
}
// Go on with the flow
return bindingExpression;
}
void element_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
object datacontext = e.NewValue;
if (datacontext != null && _targetProperty != null)
{
PropertyInfo property = datacontext.GetType().GetProperty(Binding.Path.Path);
if (property != null)
{
var attribute = property.GetCustomAttributes(true).Where(o => o is IEditatble).FirstOrDefault();
if (attribute != null)
{
Control cntrl = sender as Control;
((IEditatble)attribute).SetValue(cntrl, _targetProperty);
}
}
}
}
}
And you can use it like:
[Editable(true)]
public string Name { get; set; }
Xaml:
<TextBox IsReadOnly="{local:ReadonlyBinding Path=Name}" />
For your EditableAttribute to work, TextBox classes should use reflection on your model to check whether the attribute is set and set necessary properties. What I'm trying to say is that attribute is no more than metadata and it doesn't control the application workflow unless the application wishes so.
You could inherit from basic TextBox and insert necessary functionality though it is an overkill. You should just declare IsSomePropertyReadOnly variable and bind to it in TextBox.
Though if you're feeling really fancy, you could write some wrapper class like
public class ReadOrWriteText<T>
{
private T _value;
bool IsReadOnly { get; set; }
public T Value
{
get { return _value; }
set { if (IsReadOnly) return; _value = value; }
}
}
and bind to it's IsReadOnly and Value properties. Though it is an overkill also.

How to use INotifyPropertyChanged on a property in a class within a class..?

My issue seems to be "scope", though I'm not certain that's the right terminology. I want to notify a read-only list to re-evaluate itself when a property within a custom object is set. I believe it is simply not aware of it's existence. Maybe there is an easy way around this I cannot think of, but I'm drawing a blank.
I find this hard to put into words, so here's simplified code with my comments on what I expect to happen.
Properties within object in which I am databinding to:
private CvarAspectRatios _aspectRatio = new CvarAspectRatios("none", GetRatio());
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{ // This setter never gets hit since I bind to this
if (value != null) // object's 'Value' property now.
{
_aspectRatio = value;
NotifyPropertyChanged("AspectRatio");
NotifyPropertyChanged("ResolutionList"); // I want to inform ResolutionList
} // that it needs to repopulate based
} // on this property: AspectRatio
}
private ResolutionCollection _resolutionList = ResolutionCollection.GetResolutionCollection();
public ResolutionCollection ResolutionList
{
get
{
ResolutionCollection list = new ResolutionCollection();
if (AspectRatio != null && AspectRatio.Value != null)
{
foreach (Resolutions res in _resolutionList.Where(i => i.Compatibility == AspectRatio.Value.Compatibility))
{
list.Add(res);
}
return list;
}
return _resolutionList;
}
}
CvarAspectRatios Class:
public class CVarAspectRatios : INotifyPropertyChanged
{
private string _defaultValue;
public string DefaultValue
{
get { return _defaultValue; }
set { _defaultValue = value; NotifyPropertyChanged("DefaultValue"); }
}
private AspectRatios _value;
public AspectRatios Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged("Value");
NotifyPropertyChanged("ResolutionList"); // This value gets set, and I'd like for ResolutionList to update
} // but it cannot find ResolutionList. No errors or anything. Just
} // no update.
public AspectRatios() { }
public AspectRatios(string defaultValue, AspectRatios val)
{
DefaultValue = defaultValue;
Value = val;
}
// Implementation of INotifyPropertyChanged snipped out here
}
What do you folks think? If you'd like a sample application I can whip one up.
Since CVarAspectRatios implements INotifyPropertyChanged, you can have the viewmodel class subscribe to the PropertyChanged event for the AspectRatio.
public class YourViewModel
{
public YourViewModel()
{
AspectRatio.PropertyChanged += AspectRatio_PropertyChanged;
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
NotifyPropertyChanged("ResolutionList");
}
}
Just bear in mind that if you discard that AspectRatio object (if the object reference changes and not just the value property of that object), you should unsubscribe from the event on the discarded one.
To just transform your existing code into something which should work:
private CvarAspectRatios _aspectRatio; //No field initialization because that would not attach event handler, you could do it though and take care of the handler alone in the ctor
public CvarAspectRatios AspectRatio
{
get { return _aspectRatio; }
set
{
if (_aspectRatio != value) // WTH # "value != null"
{
_aspectRatio.PropertyChanged -= AspectRatio_PropertyChanged;
_aspectRatio = value;
_aspectRatio.PropertyChanged += new PropertyChangedEventHandler(AspectRatio_PropertyChanged);
NotifyPropertyChanged("AspectRatio");
}
}
}
void AspectRatio_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
NotifyPropertyChanged("ResolutionList");
}
}
Why don't you factor out re-populating ResolutionList into a separate private method which gets called from the setter of AspectRatios?
If a list needs to update based on a changed property, the list (or a list manager object, for better encapsulation) would normally need to subscribe to the PropertyChanged event of the object hosting the property. If the list is itself a property of the same object, as in this case, it would be simpler and leaner for the property's setter to call a method that updates the list.

Binding to a ScrollViewer's ViewportWidth and ViewportHeight

I am using the Model-View-ViewModel architecture in a WPF application I am building, and I would like a specific ViewModel to actually be reactive to the size of the view (not a normal use-case of the MVVM approach, I know).
Essentially, I have a ScrollViewer object and I want the viewmodel to observe the width and height of the scrollviewer and then be able to do things accordingly depending on what that width and height are.
I'd like to do something like this:
<ScrollViewer ViewportWidth="{Binding Path=MyViewportWidth, Mode=OneWayToSource}" ViewportHeight="{Binding Path=MyViewportHeight, Mode=OneWayToSource}" />
But of course this is impossible to do because "ViewportWidth" and "ViewportHeight" cannot be "bound to" (a.k.a. act as binding targets) because they are read-only dependency properties (even though I am not writing to them at all in this binding since it is OneWayToSource).
Anyone know of a good method to be able to do something like this?
You could try running something OnLoaded or OnResizeChanged that updates the viewmodel
private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
ViewModel vm = sv.DataContext as ViewModel;
vm.ScrollViewerHeight = sv.ViewportHeight;
vm.ScrollViewerWidth = sv.ViewportWidth;
}
Ok, this is a really old question, but I thought I'd share for posterity, since I've solved this one myself. The best solution I've found is to create a user control that derives from the ScrollView class and implements the properties you want - which are of course linked to the non-bindable properties of the base class.
You can use the OnPropertyChanged function to monitor those properties and keep the values in sync.
Here's the full code-behind of my custom usercontrol called DynamicScrollViewer. Notice that I have four bindable dependency properties called DynamicHorizontalOffset, DynamicVerticalOffset, DynamicViewportWidth, and DynamicViewportHeight.
The two offset properties allow both read and write control of the offset, while the viewport properties are essentially read-only.
I had to use this class when creating a complex animation editor control in which various components (labels at the left, nodes in the middle, timeline at top) needed to scroll synchronously, but only in limited aspects, and were all bound to common external scrollbars. Think of locking a section of rows in spreadsheet, and you get the idea.
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public partial class DynamicScrollViewer : ScrollViewer
{
public DynamicScrollViewer()
{
InitializeComponent();
}
public double DynamicHorizontalOffset
{
get { return (double)GetValue(DynamicHorizontalOffsetProperty); }
set { SetValue(DynamicHorizontalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicHorizontalOffsetProperty =
DependencyProperty.Register("DynamicHorizontalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicVerticalOffset
{
get { return (double)GetValue(DynamicVerticalOffsetProperty); }
set { SetValue(DynamicVerticalOffsetProperty, value); }
}
public static readonly DependencyProperty DynamicVerticalOffsetProperty =
DependencyProperty.Register("DynamicVerticalOffset", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportWidth
{
get { return (double)GetValue(DynamicViewportWidthProperty); }
set { SetValue(DynamicViewportWidthProperty, value); }
}
public static readonly DependencyProperty DynamicViewportWidthProperty =
DependencyProperty.Register("DynamicViewportWidth", typeof(double), typeof(DynamicScrollViewer));
public double DynamicViewportHeight
{
get { return (double)GetValue(DynamicViewportHeightProperty); }
set { SetValue(DynamicViewportHeightProperty, value); }
}
public static readonly DependencyProperty DynamicViewportHeightProperty =
DependencyProperty.Register("DynamicViewportHeight", typeof(double), typeof(DynamicScrollViewer));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DynamicVerticalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetVerticalOffset(DynamicVerticalOffset);
}
else if (e.Property == DynamicHorizontalOffsetProperty)
{
if (ScrollInfo != null)
ScrollInfo.SetHorizontalOffset(DynamicHorizontalOffset);
}
else if (e.Property == HorizontalOffsetProperty)
{
DynamicHorizontalOffset = (double)e.NewValue;
}
else if (e.Property == VerticalOffsetProperty)
{
DynamicVerticalOffset = (double)e.NewValue;
}
else if (e.Property == ViewportWidthProperty)
{
DynamicViewportWidth = (double)e.NewValue;
}
else if (e.Property == ViewportHeightProperty)
{
DynamicViewportHeight = (double)e.NewValue;
}
}
}
}

Categories

Resources