I am learning how to use data binding in WPF for a TreeView. I am procedurally creating the Binding object, setting Source, Path, and Converter properties to point to my own classes. I can even go as far as setting IsAsync and I can see the GUI update asynchronously when I explore the tree. So far so good!
My problem is that WPF eagerly evaluates parts of the tree prior to them being expanded in the GUI. If left long enough this would result in the entire tree being evaluated (well actually in this example my tree is infinite, but you get the idea). I would like the tree only be evaluated on demand as the user expands the nodes. Is this possible using the existing asynchronous data binding stuff in the WPF?
As an aside I have not figured out how ObjectDataProvider relates to this task.
My XAML code contains only a single TreeView object, and my C# code is:
public partial class Window1 : Window
{
public Window1() {
InitializeComponent();
treeView.Items.Add( CreateItem(2) );
}
static TreeViewItem CreateItem(int number)
{
TreeViewItem item = new TreeViewItem();
item.Header = number;
Binding b = new Binding();
b.Converter = new MyConverter();
b.Source = new MyDataProvider(number);
b.Path = new PropertyPath("Value");
b.IsAsync = true;
item.SetBinding(TreeView.ItemsSourceProperty, b);
return item;
}
class MyDataProvider
{
readonly int m_value;
public MyDataProvider(int value) {
m_value = value;
}
public int[] Value {
get {
// Sleep to mimick a costly operation that should not hang the UI
System.Threading.Thread.Sleep(2000);
System.Diagnostics.Debug.Write(string.Format("Evaluated for {0}\n", m_value));
return new int[] {
m_value * 2,
m_value + 1,
};
}
}
}
class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
// Convert the double to an int.
int[] values = (int[])value;
IList<TreeViewItem> result = new List<TreeViewItem>();
foreach (int i in values) {
result.Add(CreateItem(i));
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new InvalidOperationException("Not implemented.");
}
}
}
Note: I have previously managed to do lazy evaluation of the tree nodes by adding WPF event handlers and directly adding items when the event handlers are triggered. I'm trying to move away from that and use data binding instead (which I understand is more in spirit with "the WPF way").
A generic solution (as I'm not sure if your code is not mock)
Create your model containing parent and children (In this case it is an int and a list of int)
Create a ViewModel having a property IsExpanded in addition to the Model's properties
Bind your IsExpanded property to the TreeViewItem's IsExpanded property in the view(xaml)
In the IsExpanded property's setter, fill in your Children list using the Dispatcher, the priority being Background. Each addition of item into your Children List should trigger the PropertyChanged event.
Check out the MVVM design pattern, if you're not familiar with. Here is a good video by Jason
Related
I have a DataGrid with editable cells bound to their respective values in the view model of the respective items.
Initially, the data is loaded and displayed to the user, who can then edit the data in the grid.
Binding is working as it should (in my case with UpdateSourceTrigger=OnPropertyChanged), but due to conversions between double (view model) and string (UI), a TwoWay binding would cause annoying UI bugs like making decimal separators or zeros after a decimal point disappear when typed by the user.
Two faulty solutions are:
Making the property a string in the view model, and doing the necessary conversions inside the view model.
Problem: brings me a strange problem of incompatible cultures between the UI and the view model (and I don't expect the view model to know the UI's culture)
Using a OneWayToSource binding. This eliminates all UI bugs as the VM stops sending back parsed and reconverted values.
Problem: I can't (or don't know how to) initialize the values in the grid with the loaded data.
So, can I somehow use a OneWayToSource binding "after" a OneTime binding, or somehow sum the two?
I tried to bind FallbackValue and TargetNullValue to the source values, but they don't accept bindings.
The decimal place disappearing is a "feature" they introduced whilst trying to fix something else. I thought it was .Net 4.0 this was introduced and people started noticing it was a breaking change but the documentation seems to imply .Net 4.5.
It usually occurs because you set updatesourcetrigger=propertychanged.
The simple fix is often to just remove that.
Because
, UpdateSourceTrigger=LostFocus
Is the default behaviour for textbox text binding.
Alternatively, you could experiment with KeepTextBoxDisplaySynchronizedWithTextProperty
https://msdn.microsoft.com/en-us/library/system.windows.frameworkcompatibilitypreferences.keeptextboxdisplaysynchronizedwithtextproperty(v=vs.110).aspx
public MainWindow()
{
FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
InitializeComponent();
}
You can set that in Mainwindow before anything is displayed.
I found out a hacky workaround that involves using two properties, the original and a string dedicated to the user for smooth behavior. Use a specific converter for that purpose. (I think I'll adopt this as a pattern for future cases)
This works for cases where the view model does not change the property, only the user changes the property. (If you want a truly two way interaction where the view model also changes the property, you need to set the string property to null whenever you need to change the property)
In the view model:
The differences from a standard code are:
adding a string property without logic
adding a notification for this property when the original property changes
Code:
private double? _TheProperty;
public double? TheProperty { get { return _TheProperty ; } set { SetTheProperty (value); } }
public string ThePropertyUserString { get; set; } //for UI only!!! Don't change via code
private void SetTheProperty(double? value)
{
if (value == null)
{
//implement validation errors if necessary
//using IDataErrorInfo and ValidatesOnDataErrors
//this type of validation is the only I found that helps enabling/disabling command buttons
}
//do your logic
_TheProperty = value;
//notify
if (PropertyChanged != null)
{
PropertyChanged("TheProperty", ...);
PropertyChanged("ThePropertyUserString", ...);
}
}
In the XAML:
<TextBox Style="{StaticResource ErrorStyle}">
<TextBox.Text>
<MultiBinding UpdateSourceTrigger="PropertyChanged"
Mode="TwoWay"
Converter="{StaticResource DoubleUserStringConverter}">
<Binding Path="TheProperty" ValidatesOnDataErrors="True"/>
<Binding Path="ThePropertyUserString"/>
/MultiBinding>
</TextBox.Text>
</TextBox>
The converter:
/// <summary>
/// multibinding, first binding is double? and second is string, both representing the same value
/// the double? value is for the viewmodel to use as normally intended
/// the string value is for the user not to have ui bugs
/// </summary>
class DoubleUserStringConverter : IMultiValueConverter
{
private OriginalConverterYouWanted converter;
public DoubleUserStringConverter()
{
converter = new OriginalConverterYouWanted(); //single binding, not multi
//for types "double" in the view model and "string" in the UI
//in case of invalid strings, the double value sent to UI is null
}
//from view model to UI:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[1] == null) //null string means UI initialization, use double
return converter.Convert(values[0], targetType, parameter, culture);
else
return values[1]; //in the rest of the time, send user string to UI
}
//from UI to view model
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
return new object[] {
converter.ConvertBack(value, targetTypes[0], parameter, culture), //can be null
value //string is always sent as is, no changes to what the user types
};
}
}
I need to do some visual effects on the control after a particular ICommand was executed. For example, my Custom control exposes AAACommand and BBBCommand properties.
<myControl AAACommand={Binding ACommand}
BBBCommand={Binding BCommand} />
where ACommand and BCommand are Commands on ViewModel. How do I know when AAACommand was executed, so I can do some UI stuff in my UserControl? there is no Executed event for ICommand to subscribe to.
Edit: AAACommand is defined like this on my user control:
public static readonly DependencyProperty AAACommandProperty =
DependencyProperty.Register("AddCommand", typeof(RelayCommand), typeof(MyCustomControl), null);
public static readonly DependencyProperty AAACommandParameterProperty =
DependencyProperty.Register("AAACommandParameter", typeof(object), typeof(MyCustomControl), null);
public RelayCommand AAACommand
{
get { return (RelayCommand)GetValue(AAACommandProperty); }
set { SetValue(AAACommandProperty, value); }
}
public object AAACommandParameter
{
get { return (object)GetValue(AAACommandParameterProperty); }
set { SetValue(AAACommandParameterProperty, value); }
}
So, there is no problem in invoking ACommand on ViewModel, this works without problem. The problem is how will my user control know when AAACommand will execute ACommand, so it can do something with its UI.
You want to update the usercontrol based upon a response returned from the view model and the command executed? I asked a question similar in nature where I wanted to pass as string value from one user control to another user control. I accomplished this using INotifyProperty Changed event. You can read the original question and solution here
Update to comment:
Base on your comment it seems like one of two things could happen. If you don't need the VM to respond then the update could be triggered by elements in the view. You could do this using Binding ElementNameProperty. This in essence allows you to trigger/change a property based upon the action of another element. (typing text in one field displays the value in another control) Here is the msdn description and example .
If you need it to be invoked based upon the return (i.e. success or failure) then the ViewModel will need to have a property (like a bool) that is bound two-way to the property of the element in the ui.
You may need to create a converter (inheriting IValueConverter ) to handle the binding but INotifyProp Change would be used to marshal the update between the controls or the bound elements within them.
Here is a quick example:
within my xaml I added a user control that I did not want to be visible within the UI until another button within a secondary usercontrol was clicked. To handle this I setup binding on the Visibility property
<ctrl:LandingPage x:Name="ucLandingPage"
Grid.Row="1"
DataContext="{Binding}"
Visibility="{Binding LandingPageVisibility, Mode=OneWay, Converter={StaticResource LandingPageVisibilityConverter}}"/>
Within the viewmodel I had the following property and code
// Default ctor
public SearchViewModel()
{
//Show that the Landing Page control is being displayed
SearchVisibility = Visibility.Collapsed;
}
property in VM (note I use SimpleMVVM framework which has Inotify included in the base object so my notify prop event may look a bit different from yours)
private Visibility _SearchVisibility;
public Visibility SearchVisibility
{
get { return _SearchVisibility; }
set
{
_SearchVisibility = value;
NotifyPropertyChanged(m => m.SearchVisibility);
}
}
Then the method within the VM that updated this property
public void GetSearchResult()
{
currentPage = 1;
//Set the visibility of the search control in the center of the page
SearchVisibility = Visibility.Visible;
this.SearchHistory = this._DataModel.AddSearchHistoryItem(this.SearchTerm);
}
And finally the converter class which would convert the return value to the correct property value for the element
public class SearchVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null & System.Convert.ToString(value) == "Visible")
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm pretty new to ObservableCollections, but have built some code which I'm sure should work. Unfortunately it doesn't. The only thing that is not happening, is my GUI is not being updated. I know the values are being updated in the back (Checked using Debugger).
What am I doing wrong?
Here with a sample of my XAML for the Textblock:
<TextBlock Name="tbCallsOpen" Text="{Binding IndicatorValue}" />
Herewith sample of my code behind:
public partial class CurrentCalls : UserControl
{
Microsoft.SharePoint.Client.ListItemCollection spListItems;
ObservableCollection<CurrentCallIndicator> CallIndicators = new ObservableCollection<CurrentCallIndicator>();
public CurrentCalls()
{
InitializeComponent();
DispatcherTimer dispatchTimer = new DispatcherTimer();
dispatchTimer.Interval = new TimeSpan(0, 0, 20);
dispatchTimer.Tick += new EventHandler(BindData);
dispatchTimer.Start();
}
private void BindData(object sender, EventArgs args)
{
//splistitems is a sharepoint list. Data is being retrieved succesfully, no issues here.
foreach (var item in spListItems)
{
//My custom class which implements INotifyPropertyChanged
CurrentCallIndicator indicator = new CurrentCallIndicator();
indicator.IndicatorValue = item["MyValueColumn"];
//Adding to ObservableCollection
CallIndicators.Add(indicator);
}
//Setting Datacontext of a normal TextBlock
tbCallsOpen.DataContext = CallIndicators.First(z => z.IndicatorName == "somevalue");
}
}
You are most likely assuming that changes to the underlying items in the collection will raise the CollectionChanged event; however that is not how the ObservableCollection<T> works.
If you wanted this behavior you would need to roll your own implmentation and when a PropertyChanged event is fired within an item within your collection, you would then need to fire the CollectionChanged event.
Your code looks more-or-less correct to me, at first blush - though I wouldn't expect that you'd need to use an ObservableCollection<> to get the results you seem to be expecting: a simple List<> would work just fine.
If the debugger tells you that the DataContext is being updated correctly to the expected item, then the most likely issue is that there's a problem with how your binding is defined. If you're not seeing any binding errors reported in your debug window, then I'd look into Bea Stollnitz' article on debugging bindings. Most specifically, I often use the technique she suggests of a "DebugValueConverter", e.g.:
/// <summary>
/// Helps to debug bindings. Use like this: Content="{Binding PropertyName, Converter={StaticResource debugConverter}}"
/// </summary>
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
And then set a breakpoint in your converter, and watch what's happening. It's a hack and a kludge, but until we're all on SL5 (which has binding debugging built in), it's your best bet.
Ok, Sorted. I fixed the issue myself. Because I was updating the values in a loop, the ObservableCollection wasn't being updated properly. All I did in the beginning of the databinding method, was to Clear the collection : CallIndicators.Clear();
I spent last two weeks trying to figure out a method to display the items of a listbox in reversed order without using any sort property and without putting any presentation logic in my entities, I just want that the last inserted item is displayed at the top of the listbox.
The only pure XAML solution I've found is this WPF reverse ListView but it isn't so elegant. I've also tried to override the GetEnumerator() of my BindableCollection (I use Caliburn Micro as MVVM framework) to return an enumerator that iterate over my collection's items in the reverse order but id did not work.
How can I do?
ScaleTransform is an elegant solution to this particular case, but there could be more generic applications (such as binding the same list but with different permutations applied).
It is possible to do this all with converters if you make sure to consider that the binding is to the list, rather than the elements of the list. Assuming that your using an ObservableCollection of strings (sure it would be possible to use generics and reflection to make this more elegant) and missing out all the proper coding of exception handling and multiple calls to Convert...
public class ReverseListConverter : MarkupExtension, IValueConverter
{
private ObservableCollection<string> _reversedList;
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
_reversedList = new ObservableCollection<string>();
var data = (ObservableCollection<string>) value;
for (var i = data.Count - 1; i >= 0; i--)
_reversedList.Add(data[i]);
data.CollectionChanged += DataCollectionChanged;
return _reversedList;
}
void DataCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var data = (ObservableCollection<string>)sender;
_reversedList.Clear();
for (var i = data.Count - 1; i >= 0; i--)
_reversedList.Add(data[i]);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You can then bind within your XAML with something like
<ListBox ItemsSource="{Binding Converter={ReverseListConverter}}"/>
Is there any way to change the value of property at runtime in WPF data binding. Let's say my TextBox is bind to a IsAdmin property. Is there anyway I can change that property value in XAML to be !IsAdmin.
I just want to negate the property so Valueconverter might be an overkill!
NOTE: Without using ValueConverter
You can use an IValueConverter.
[ValueConversion(typeof(bool), typeof(bool))]
public class InvertBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool original = (bool)value;
return !original;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool original = (bool)value;
return !original;
}
}
Then you'd setup your binding like:
<TextBlock Text="{Binding Path=IsAdmin, Converter={StaticResource boolConvert}}" />
Add a resource (usually in your UserControl/Window) like so:
<local:InvertBooleanConverter x:Key="boolConvert"/>
Edit in response to comment:
If you want to avoid a value converter for some reason (although I feel that it's the most appropriate place), you can do the conversion directly in your ViewModel. Just add a property like:
public bool IsRegularUser
{
get { return !this.IsAdmin; }
}
If you do this, however, make sure your IsAdmin property setter also raises a PropertyChanged event for "IsRegularUser" as well as "IsAdmin", so the UI updates accordingly.
If you specifically want to do this at XAML end (I am not sure the reason for that, unless you have 100s of similar operation of negate) there are only two ways 1) Using IValueConverter 2)write a XAML Markup Extension (Way too much work for this small task :))
Then the other obvious way is to write another property in your ViewModel , which can return the Negative of the IsAdmin property.
You can't bind to !Property, but you could create a new Binding with an appropriate IValueConverter and change out the entire Binding at runtime. The key is the BindingOperations class, which allows you to change the binding on a particular DependencyProperty.
public static void InvertBinding(DependencyObject target, DependencyProperty dp)
{
//We'll invert the existing binding, so need to find it
var binding = BindingOperations.GetBinding(target, dp);
if (binding != null)
{
if (binding.Converter != null)
throw new InvalidOperationException("This binding already has a converter and cannot be inverted");
binding.Converter = new InvertingValueConverter(); //This would be your custom converter
//Not sure if you need this step, but it will cause the binding to refresh
BindingOperations.SetBinding(target, dp, binding);
}
}
This should give you a general idea; I wouldn't use this for production code, as you'd probably want to generalize it to toggle the converter or whatever else you need to change out at runtime. You could also avoid changing the binding entirely by creating a new property you bind to that encapsulates this 'switching' logic. The last option is probably the best.
You can write a ValueConverter that automatically negates the input before returning it. Have a look at BenCon's blog for a short reading on value converters.