I hope this doesn't get marked as duplicate since my problem is kinda complex, so none of the other answers helped. I have a class called 'ControlChoiceModule' that generates a System.Windows.Controls object based on the type of property it deals with (String - TextBox, Boolean - CheckBox, DateTime - DatePicker, etc..).
It has two dictionaries:
public static class ControlChoiceModule
{
private static readonly Dictionary<Type, object> TypeToControl = new Dictionary<Type, object>
{
{typeof(bool), new CheckBox() },
{typeof(DateTime), new DatePicker() }
};
private static readonly Dictionary<Type, DependencyProperty> ControlToProperty = new Dictionary<Type, DependencyProperty>
{
{typeof(TextBox), TextBox.TextProperty },
{typeof(CheckBox), CheckBox.IsCheckedProperty },
{typeof(DatePicker), DatePicker.SelectedDateProperty }
};
The purpose of the other one is just for binding. And here are the two methods:
public static object GenerateControl(Type theType, Binding B)
{
object O;
if (TypeToControl.ContainsKey(theType))
{
O = TypeToControl[theType];
}
else
{
O = new TextBox();
}
SetBinding(O, B);
return O;
}
private static void SetBinding(object O, Binding B)
{
BindingOperations.SetBinding(O as DependencyObject, ControlToProperty[O.GetType()], B);
}
Now the purpose of all this is to generate an insertion window for a certain class, generically. So the windows loops through all of the class's properties and, based on the type, generates an appropriate field for it.
private void GenerateInsertionOrUpdatePage(string windowText)
{
var w2 = new InsertionWindow();
w2.DataContext = this.DataContext;
w2.Title = windowText;
w2.Show();
foreach (var P in ReturnPropertyList())
{
if (P.Name != "SearchableString" && P.Name != "Id" )
{
Label L = new Label();
L.Content = P.Name + ":";
w2.InsertionStackPanel.Children.Add(L);
Binding B = new Binding();
B.Path = new PropertyPath("NewT." + P.Name);
B.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
B.Mode = BindingMode.TwoWay;
**w2.InsertionStackPanel.Children.Add(ControlChoiceModule.GenerateControl(P.PropertyType, B) as UIElement);**
}
}
}
The method above gets called on a click of a button. The first time I click it, it works just fine. But when I click it again, I get the error from the title (on the line marked with **).
Any idea why this happens?
Thanks
Related
I've run into an issue which seems pretty strange and I'm not sure if this is a bug or a design feature of WPF.
I have a Dependency Object with a Dependency Property with constraints enforced via value coercion and everything works as expected when using this property in bindings with other dependency properties.
But things break down when trying to use the property in a binding against a POCO object. In this case the POCO is updated when the dependency property changes, but before value coercion is executed, leaving the POCO with an invalid/out-of-sync value.
Here's an example:
public class TestDO : DependencyObject
{
public static DependencyProperty NumberProperty =
DependencyProperty.Register("Number", typeof(int), typeof(TestDO),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.None, OnNumberChanged, OnNumberCoerce));
public int Number
{
get { return (int)GetValue(NumberProperty); }
set { SetValue(NumberProperty, value); }
}
private static void OnNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("DepObj: NumberChanged {0} --> {1}", e.OldValue, e.NewValue);
}
private static object OnNumberCoerce(DependencyObject d, object value)
{
int cval = (int)value;
if (cval > 10)
cval = 10;
else if (cval < -10)
cval = 10;
Console.WriteLine("DepObj: OnNumberCoerce: {0} --> {1}", value, cval);
return cval;
}
}
public class TestPOCO
{
private int _pocoNumber = 0;
public int PocoNumber
{
get { return _pocoNumber; }
set { _pocoNumber = value; }
}
}
Now, we can create two instances of the types above and a binding and test setting the Number property. The binding updates the POCO object but it does so before value coercion.
// create a POCO object
var testPoco = new TestPOCO();
// create a dependency object
var testObj = new TestDO();
// create a binding between the NumberProperty of the Dependency Object and the
// PocoNumber of the POCO object
BindingOperations.SetBinding(testObj, TestDO.NumberProperty, new Binding("PocoNumber")
{
Source = testPoco,
Mode = BindingMode.TwoWay
});
// try setting a value of 100, to trigger value coercion
testObj.Number = 100;
Console.WriteLine("Poco Number = {0}", testPoco.PocoNumber); // displays 100
Console.WriteLine("DP Number = {0}", testObj.Number); // displays 10
The same is not true if the binding exists between two DependencyObjects. In that case everything works as expected. SetValue --> ValueCoercion --> PropertyChanged --> SetValue
I have a simple search text box. This text box acts as a filter. I've now copied/pasted the code for the 5th time and enough is enough. Time for a custom control.
left and right brackets have been replaced with ()
My custom control will be simple. My problem is I want to have a dependencyProperty on this control that is of type List(T).
I created a test project to proof it out and make sure it works. It works well. Ignore List.
Below is the entire class. The problem is that the only thing holding me up is replacing List (Person) with List(T). Something like List where: T is Object
typeof(List(T) where: T is Object) <= Obviously I can't do that but gives an idea what I'm trying to accomplish.
public class SearchTextBox : TextBox
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("FilterSource", typeof(List<Person>), typeof(SearchTextBox), new UIPropertyMetadata(null)); //I WANT THIS TO BE LIST<T>
public List<Person> FilterSource
{
get
{
return (List<Person>)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public static readonly DependencyProperty FilterPropertyNameProperty =
DependencyProperty.Register("FilterPropertyName", typeof(String), typeof(SearchTextBox), new UIPropertyMetadata());
public String FilterPropertyName
{
get
{
return (String)GetValue(FilterPropertyNameProperty);
}
set
{
SetValue(FilterPropertyNameProperty, value);
}
}
public SearchTextBox()
{
this.KeyUp += new System.Windows.Input.KeyEventHandler(SearchBox_KeyUp);
}
void SearchBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(FilterSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterTheSource);
}
bool FilterTheSource(object obj)
{
if (obj == null) return false;
Type t = obj.GetType();
PropertyInfo pi = t.GetProperty(FilterPropertyName);
//object o = obj.GetType().GetProperty(FilterPropertyName);
String propertyValue = obj.GetType().GetProperty(FilterPropertyName).GetValue(obj, null).ToString().ToLower();
if (propertyValue.Contains(this.Text.ToLower()))
{
return true;
}
return false;
}
}
No; that's not possible.
Instead, just use the non-generic typeof(IList).
I am working on a custom wpf control which is derived from a ListBox and am trying to apply some formatting to a custom property.
When a particular custom property is false, I want to apply some formatting to the ListBox.
I am using the following code to attempt to apply the styling -
var t = new Trigger();
var BackgroundSetter = new Setter {Property = BackgroundProperty, Value = null};
var BrushSetter = new Setter { Property = BorderBrushProperty, Value = null };
t.Setters.Add(BackgroundSetter);
t.Setters.Add(BrushSetter);
var s = new Style(typeof(ListBox));
s.Triggers.Add(t);
editor.ItemContainerStyle.Triggers.Add(t);
I have also tried the following with no luck -
editor.ItemContainerStyle = s;
I am getting an error that indicates that some object was not initialized and stepping through shows that editor.ItemContainerStyle is null.
The actual error message just says Exception has been thrown by the target of an invocation.
Does anyone have any idea what I might be doing wrong?
Thanks
I was able to get this working - below is the code that I actually ended up using -
public bool IsSelectable
{
get { return (bool)GetValue(IsSelectableProperty); }
set { SetValue(IsSelectableProperty, value); }
}
public static DependencyProperty IsSelectableProperty = DependencyProperty.Register("IsSelectable", typeof(bool), typeof(ListEditor), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(IsSelectablePropertyChanged)) { BindsTwoWayByDefault = true });
private static void IsSelectablePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var editor = sender as ListEditor;
var s = new Style(typeof(ListBoxItem));
var enableSetter = new Setter {Property = IsEnabledProperty, Value = editor.IsSelectable};
s.Setters.Add(enableSetter);
editor.ItemContainerStyle = s;
}
I'm animating a 'race' on a map. The race takes 45 minutes, but the animation runs for 60 seconds.
You can watch the 2008 City2Surf race demo to see what I mean.
The 'race clock' in the top-left must show "real time", and had to be set-up in the .xaml.cs with a System.Windows.Threading.DispatcherTimer which seems a bit of a hack.
I thought maybe there'd be a DependencyProperty on the animation rather than just StoryBoard.GetCurrentTime(), but instead I have had to
// SET UP AND START TIMER, before StoryBoard.Begin()
dt = new System.Windows.Threading.DispatcherTimer();
dt.Interval = new TimeSpan(0, 0, 0, 0, 100); // 0.1 second
dt.Tick +=new EventHandler(dt_Tick);
winTimeRatio = (realWinTime.TotalSeconds * 1.0) / animWinTime.TotalSeconds;
dt.Start();
and then the Tick event handler
void dt_Tick(object sender, EventArgs e)
{
var sb = LayoutRoot.Resources["Timeline"] as Storyboard;
TimeSpan ts = sb.GetCurrentTime();
TimeSpan toDisplay = new TimeSpan(0,0,
Convert.ToInt32(ts.TotalSeconds * winTimeRatio));
RaceTimeText.Text = toDisplay.ToString();
}
This works and seems to perform OK - but my question is: am I missing something in the Silverlight animation/storyboard classes that would do this more neatly? I have to remember to stop the DispatcherTimer too!
Or to put the question another way: any better suggestions on 'animation' of TextBox content (the .Text itself, not the location/dimensions/etc)?
That is one way. It's nice and simple, but a bit messy. You could get rid of the storyboard and on each tick, increment a local value by the tick interval and use that to set your time. You would then only have one time piece.
Or... A more elegant and re-usable way would be to create a helper class that is a DependencyObject. I would also just use a StoryBoard with a DoubleAnimation an bind the Storyboard.Target to an instance of the DoubleTextblockSetter. Set the storyboard Duration to your time and set the value to your time in seconds. Here is the DoublerBlockSetterCode.
public class DoubleTextBlockSetter : DependencyObject
{
private TextBlock textBlock { get; private set; }
private IValueConverter converter { get; private set; }
private object converterParameter { get; private set; }
public DoubleTextBlockSetter(
TextBlock textBlock,
IValueConverter converter,
object converterParameter)
{
this.textBlock = textBlock;
this.converter = converter;
this.converterParameter = converterParameter;
}
#region Value
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(double),
typeof(DoubleTextBlockSetter),
new PropertyMetadata(
new PropertyChangedCallback(
DoubleTextBlockSetter.ValuePropertyChanged
)
)
);
private static void ValuePropertyChanged(
DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
DoubleTextBlockSetter control = obj as DoubleTextBlockSetter;
if (control != null)
{
control.OnValuePropertyChanged();
}
}
public double Value
{
get { return (double)this.GetValue(DoubleTextBlockSetter.ValueProperty); }
set { base.SetValue(DoubleTextBlockSetter.ValueProperty, value); }
}
protected virtual void OnValuePropertyChanged()
{
this.textBlock.Text = this.converter.Convert(
this.Value,
typeof(string),
this.converterParameter,
CultureInfo.CurrentCulture) as string;
}
#endregion
}
Then you might have a format converter:
public class TicksFormatConverter : IValueConverter
{
TimeSpanFormatProvider formatProvider = new TimeSpanFormatProvider();
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
long numericValue = 0;
if (value is int)
{
numericValue = (long)(int)value;
}
else if (value is long)
{
numericValue = (long)value;
}
else if (value is double)
{
numericValue = (long)(double)value;
}
else
throw new ArgumentException("Expecting type of int, long, or double.");
string formatterString = null;
if (parameter != null)
{
formatterString = parameter.ToString();
}
else
{
formatterString = "{0:H:m:ss}";
}
TimeSpan timespan = new TimeSpan(numericValue);
return string.Format(this.formatProvider, formatterString, timespan);
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
I almost forgot the TimespanFormatProvider. There is no format provider for timespan in Silverlight, so it appears.
public class TimeSpanFormatProvider : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType != typeof(ICustomFormatter))
return null;
return this;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
string formattedString;
if (arg is TimeSpan)
{
TimeSpan ts = (TimeSpan)arg;
DateTime dt = DateTime.MinValue.Add(ts);
if (ts < TimeSpan.FromDays(1))
{
format = format.Replace("d.", "");
format = format.Replace("d", "");
}
if (ts < TimeSpan.FromHours(1))
{
format = format.Replace("H:", "");
format = format.Replace("H", "");
format = format.Replace("h:", "");
format = format.Replace("h", "");
}
// Uncomment of you want to minutes to disappear below 60 seconds.
//if (ts < TimeSpan.FromMinutes(1))
//{
// format = format.Replace("m:", "");
// format = format.Replace("m", "");
//}
if (string.IsNullOrEmpty(format))
{
formattedString = string.Empty;
}
else
{
formattedString = dt.ToString(format, formatProvider);
}
}
else
throw new ArgumentNullException();
return formattedString;
}
}
All that stuff is re-usable and should live in your tool box. I pulled it from mine. Then, of course, you wire it all together:
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
sb.Children.Add(da);
DoubleTextBlockSetter textBlockSetter = new DoubleTextBlockSetter(
Your_TextBlock,
new TicksFormatConverter(),
"{0:m:ss}"); // DateTime format
Storyboard.SetTarget(da, textBlockSetter);
da.From = Your_RefreshInterval_Secs * TimeSpan.TicksPerSecond;
da.Duration = new Duration(
new TimeSpan(
Your_RefreshInterval_Secs * TimeSpan.TicksPerSecond));
sb.begin();
And that should do the trick. An it's only like a million lines of code. And we haven't even written Hello World just yet...;) I didn't compile that, but I did Copy and Paste the 3 classes directly from my library. I've used them quite a lot. It works great. I also use those classes for other things. The TickFormatConverter comes in handy when data binding. I also have one that does Seconds. Very useful. The DoubleTextblockSetter allows me to animate numbers, which is really fun. Especially when you apply different types of interpolation.
Enjoy.
I'm trying to bind a List<T> to a DataGridView control, and I'm not having any luck creating custom bindings.
I have tried:
gvProgramCode.DataBindings.Add(new Binding("Opcode",code,"Opcode"));
It throws an exception, saying that nothing was found by that property name.
The name of the column in question is "Opcode". The name of the property in the List<T> is Opcode.
ANSWER EDIT: the problem was that I did not have the bindable fields in my class as properties, just public fields...Apparently it doesn't reflect on fields, just properties.
Is the property on the grid you are binding to Opcode as well?.. if you want to bind directly to List you would just DataSource = list. The databindings allows custom binding. are you trying to do something other than the datasource?
You are getting a bunch of empty rows? do the auto generated columns have names? Have you verified data is in the object (not just string.empty) ?
class MyObject
{
public string Something { get; set; }
public string Text { get; set; }
public string Other { get; set; }
}
public Form1()
{
InitializeComponent();
List<MyObject> myList = new List<MyObject>();
for (int i = 0; i < 200; i++)
{
string num = i.ToString();
myList.Add(new MyObject { Something = "Something " + num , Text = "Some Row " + num , Other = "Other " + num });
}
dataGridView1.DataSource = myList;
}
this should work fine...
I can't really tell what you're trying to do with the example you included, but binding to a generic list of objects is fairly straightforward if you just want to list the objects:
private BindingSource _gridSource;
private BindingSource GridSource
{
get
{
if (_gridSource == null)
_gridSource = new BindingSource();
return _gridSource;
}
}
private void Form1_Load(object sender, EventArgs e)
{
List<FluffyBunny> list = new List<FluffyBunny>();
list.Add(new FluffyBunny { Color = "White", EarType = "Long", Name = "Stan" });
list.Add(new FluffyBunny { Color = "Brown", EarType = "Medium", Name = "Mike" });
list.Add(new FluffyBunny { Color = "Mottled", EarType = "Short", Name = "Torvald" });
GridSource.DataSource = list;
dataGridView1.Columns["EarType"].Visible = false; //Optionally hide a column
dataGridView1.DataSource = GridSource;
}
If you only want to display specific properties of the List's type you should be able to make the unwanted column(s) invisible.
Technically, you don't really need to create the BindingSource, but I find it's a whole lot easier when I'm doing updates or changes if I have it.
Hope this helps.
Had the same issue... I had a struct with public fields obviously. nothing in the grid. provided public getters, worked.
Another solution I've found is to use the BindingList collection.
private void Form1_Load(object sender, EventArgs e)
{
BindingList people= new BindingList {
new Person {Name="John",Age=23},
new Person {Name="Lucy",Age=16}
};
dataGridView1.DataSource= people;
}
It works fine for me,