In WPF it is easy to use a ValueConverter to format values etc, (in our case convert some numbers into a different unit, e.g km to miles)
I know it can be done in Winforms, but all my Googleing just brings up results for WPF and Silverlight.
You can use a TypeConverter if you're able and willing to decorate the data source property with a custom attribute.
Otherwise you have to attach to the Parse and Format events of a Binding object. This, unfortunately, eliminates using the designer for your binding for all but the simplest scenarios.
For example, let's say you wanted a TextBox bound to an integer column representing kilometers and you wanted the visual representation in miles:
In the constructor:
Binding bind = new Binding("Text", source, "PropertyName");
bind.Format += bind_Format;
bind.Parse += bind_Parse;
textBox.DataBindings.Add(bind);
...
void bind_Format(object sender, ConvertEventArgs e)
{
int km = (int)e.Value;
e.Value = ConvertKMToMiles(km).ToString();
}
void bind_Parse(object sender, ConvertEventArgs e)
{
int miles = int.Parse((string)e.Value);
e.Value = ConvertMilesToKM(miles);
}
Another option is to have a specific ViewModel for the form which exposes data in the format you need to display on the form. You can easily achieve it by using AutoMapper and building your own Formatter.
This way you will have full support for designer too.
Related
I ran into a situation where I wanted to setup some data binding between a POCO property and a TextBox. You can do this with a BindingSource and a control's DataBindings collection, of course, but a BindingSource expects both a DataSource and DataMember, and is not POCO-friendly, really. (A BindingSource seems to expect a tabular data source instead of a single row, as it were.) So, I had a DataSource, but no DataMember.
I accomplished this with this a TextBox extension method using reflection, passing the POCO object and property to bind to as a string:
public static void Bind(this TextBox textBox, object dataObject, string propertyName)
{
PropertyInfo property = dataObject.GetType().GetProperty(propertyName);
textBox.Text = property.GetValue(dataObject, null).ToString();
textBox.TextChanged += delegate(object sender, EventArgs e)
{
PropertyInfo pi = dataObject.GetType().GetProperty(propertyName);
pi.SetValue(dataObject, textBox.Text, null);
};
}
In my situation, the calling code looks like this:
tbProjectConnection.Bind(_modelBuilder, "ProjectConnection");
tbOutputFolder.Bind(_modelBuilder, "OutputFolder");
chkWebMatrix.Bind(_modelBuilder, "UseWebMatrix");
tbNamespace.Bind(_modelBuilder, "ProjectNamespace");
tbDbClassName.Bind(_modelBuilder, "DbClassName");
In my example, _modelBuilder is a DataSet object and the properties shown ("ProjectConnection", "OutputFolder", etc) are simple string properties of the DataSet--not of records in the dataset, but of the dataset as a whole.
I'm not crazy about passing a property name as a string, and wonder if it's possible to re-write this function as a lambda, so it looks like this when called:
tbProjectConnection.Bind(property => _modelBuilder.ProjectConnection);
tbOutputFolder.Bind(property => _modelBuilder.OutputFolder);
I'm a novice with lambdas, so any help is much appreciated!
I don't think there is an easy way to do this, but there is a very interesting writeup at:
http://joelabrahamsson.com/getting-property-and-method-names-using-static-reflection-in-c/
This might give you what you are looking for, although it doesn't look simple.
I may be missing something about the fundamentals of WPF design, but I was wondering why many properties on WPF controls are exposed as the type 'Object'?
For example, MenuItem.Icon is an Object, and so is MenuItem.ToolTip. As a near first time user, this was very confusing to me (it felt like I was using a dynamic programming language, having no idea whether setting ToolTip to a String type would even work or not). Moreover, I tried to set the Icon to a 'System.Drawing.Icon' and I get an ArgumentException of "Argument 'picture' must be a picture that can be used as a Icon." Shouldn't the property be typed so it can at least describe what in the world you're supposed to give it?
Honestly, my guess as to the reason is because you cannot implement an interface on a type you did not create (without creating a wrapper), but that's just a guess.
Thanks very much for your answers and insights!
The main reason in my opinion is that since an Object is the "ultimate base class of all classes in the .Net Framework". This gives you flexibility, in WPF you are not limited to a predefined type. Wpf is different and has a learning curve, but it does give you a lot more options to create a product that looks good.
i.e.
You can assign a TextBox to a ToolTip:
TextBox tb = new TextBox();
tb.Text = "Hello World";
this.ToolTip = tb;
a Bitmap
BitmapImage myBitmapImage = new BitmapImage(new Uri((#"C:\Temp\20100706.jpg")));
Image image = new Image();
image.Source = myBitmapImage;
this.ToolTip = image;
and assigning a Image to a MenuItem
BitmapImage myBitmapImage = new BitmapImage(new Uri((#"C:\Temp\20100706.jpg")));
Image image = new Image();
image.Source = myBitmapImage;
menuItem1.Icon = image;
Consider the ToolTip for example. A ToolTip is a ContentControl, which can contain any type of CLR (Common Language Runtime) object (such as a string or a DateTime object) or a UIElement object (such as a Rectangle or a Panel). This enables you to add rich content to controls such as Button and CheckBox.
For this reason, elements such as ToolTip are exposed as Object, that is the root of the type hierarchy (with resulting ease of use, flexibility and clarity of the code).
Imagine these properties were typed as UIElements (or some other WPF specific object). How would you add objects to your controls that were not UIElements?
You would have to provide a wrapper derived from a WPF object that exposes the information you require. Most of the time the wrapper would simply call ToString() of the object being wrapped. Seeing as most types you will be using provide a good enough default implementation of ToString() it makes sense to just call this instead of making the developer write wrappers for everything.
Second, imagine if they were typed as some interface. What if you want to communicate something that this interface can't? The only options are (a) the developer lives with the limitations of the framework or (b) Microsoft updates the interface and breaks all existing code which has already been written.
Also consider if you are using a pattern like MVVM. The current design means your view models can expose properties that are not tied to WPF in any way which ultimately makes your code more reusable across different technologies.
Finally, remember that there is a difference between the object that represents the property and they way that WPF renders that information. E.G. if you use a primitive type such as System.String, WPF will create a textblock and set the text property to the result of ToString(). This allows a very clean separation between the data that is displayed by the UI and they way the information is rendered by the UI.
Take a simple class that represents a menu item, for example:
public class MenuItem
{
public string Text { get; set; }
public bool IsChecked { get; set; }
public bool IsEnabled { get; set; }
}
This type only exposes data about the menu item and has no information about how this information should be rendered. In fact, apart from the name of the class (MenuItem) this is not even specific to a menu item and the same data could be used in another UI control such as a checked listbox with no changes required. If the class exposed WPF specific user interface elements then the information would need to be adapted by another type for each different user interface control.
I'm fairly new to Silverlight but experienced in web development, and I'm finding myself highly annoyed with Silverlight's default combobox. It seems to be lacking any concept of use for regular data entry. Primarily I'm wishing it would function like an HTML select box, where you can hit the drop down, then type a letter and it takes you down to the first item with that letter. Is there an easy way I'm missing to make it function like this, or a third party control that can do this?
Thanks!
You could write an attached behavior to provide this functionality. The problem is that the items in a ComboBox in Silverlight aren't always strings. They may be entire controls that the user has templated as the ItemTemplate. If you know yours are going to be string you can implement a Behavior<ComboBox> to attach to the KeyDown event and select the correct one.
public class HTMLSelectBehavior : Behavior<ComboBox>
{
protected override void OnAttached()
{
AssociatedObject.KeyDown += OnKeyDown;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
SelectedItem = AssociatedObject.ItemsSource
.FirstOrDefault(i => i.ToString().BeginsWith((char)e.Key));
}
}
This is off the top of my head so it may not be exactly right and definitely lacks many safety checks, but it should give you an idea.
I'm very new to databinding, and so far have only had luck databinding to element properties in the GUI, or by binding to an ObservableCollection to display lists of data. Now I want to do something different.
I have a method that takes an index and returns a List of doubles, i.e.
List<double> GetValues( int index);
I have a GUI where the user selects the index from a combobox, and then several textboxes need to have their text updated with the values in the List. I actually have a thread running that caches all of this data, because I have UI elements in different places that consume and display the same information. So I figured, why not use databinding? The problem is, I have yet to find a good example online that explains how to take the index from the combobox, call the GetValues method, and then bind the resulting information to all of the textboxes -- all from XAML.
The closest article I've found is http://msdn.microsoft.com/en-us/magazine/cc163299.aspx. Most of the articles I've read talk about using the Source attribute, but then say, "well, the easiest way is to just use StaticResource, so we'll show you that method".
How can this be accomplished? Would it be advisable to just go back to the easy way of doing this entirely from code-behind?
The problem you're having is that you're trying to bind to the results from a function, and you may very well complicate things by trying to implement data-binding on something so simple from the code-behind. I'd recommend doing this from the code-behind.
That said, for the simplest and most useful approach, you need to have actual properties in your class to bind to, which you need to update when the index changes. Depending on how you pass the data around, this could reduce the code-behind, or just create more.
Here's an example of what you could end up with:
// Assume 1 of your textboxes displays a weight. Here's the property declaration:
// Disclaimer: Not compiled or tested at all.
public static readonly DependencyProperty WeightProperty = DependencyProperty.Register(
"Weight", typeof(double), typeof(MyClass), new PropertyMetadata(0.0));
public double Weight
{
get { return (double)this.GetValue(WeightProperty); }
set { this.SetValue(WeightProperty); }
}
// Here's an example of setting the property:
private void ComboBoxSelectedIndexChanged(object sender, RoutedEventArgs e)
{
List<double> values = myObject.GetValues(comboBox.SelectedIndex);
this.Weight = values[0];
}
// And in your XAML, assuming you've given your Window the name myWindow:
<TextBlock Text="{Binding ElementName=myWindow, Path=Weight}"/>
This can be useful if you plan on updating the Weight property in multiple places and want the TextBlock to always show the correct value.
On the other hand, if your properties will only update in the SelectedIndexChanged function and you don't need the values outside of that function, you may as well have just set the value yourself and reduce the unnecessary overhead:
private void ComboBoxSelectedIndexChanged(object sender, RoutedEventArgs e)
{
List<double> values = myObject.GetValues(comboBox.SelectedIndex);
txtWeight.Text = values[0].ToString();
}
I am trying to use databinding to bind data to a Silverlight toolkit chart.
I will have one to many sets of series so cannot determine how many series i need before hand.
I also want to stick to a databinding model and not resort to programmatically adding these series as many other controls bind to this datasource.
I found an article on the web by Jeremiah Morrill that showed a solution for this very problem.
Jeremiah's solution
Now this worked perfectly at first, until I tried to update my databinding's datasource values while the application was running, and this would not reflect. As if it was not subscribed to the PropertyChanged event.
I even bound the same data to a datagrid next to the chart, and the datagrid reacts as expected changing everytime my databinding's datasource values change.
In my ChartHelper from Jeremiah's solution, i have the following dependency property
public static readonly DependencyProperty SeriesSourceProperty =
DependencyProperty.RegisterAttached("SeriesSource",
typeof(IEnumerable),
typeof(ChartHelper),
new PropertyMetadata(SeriesSourceChanged));
The SeriesSourceChanged event is called when my application starts up.
However, when my DataBinding's datasource values change, this is not called again.
So questions are as follows:
How can I capture the PropertyChanged notification with this solution?
Is there something I can add to the DependencyProperty above to capture this?
Is it something i need to add to the chart or series to achieve this?
I have been racking my brain over this for several days, so any help or suggestions will be much appreciated
Thanks!
The SeriesSource type should be ObservableCollection instead of IEnumerable. Then you do something like this:
private static void SeriesSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var sender = o as YourType;
var newCollection = e.NewValue as ObservableCollection<DataSetViewModel>;
if (newCollection != null)
{
newCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(sender.OnCollectionChanged);
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
I never did find a solution to this problem and ended up using a chart control from visifire
I found this much easier to customise but never found a neat way of using databinding to achieve this and ended up with a more programattic approach.