Binding to an array in Xamarin/XAML (implementing breadcrumbs) - c#

I am trying to implement bread crumb navigation. I have a string array in the C# code like this:
public static readonly BindableProperty CurrentPathProperty =
BindableProperty.Create<FileBrowser, string[]>(c => c.CurrentPath, null);
public string[] CurrentPath
{
get { return GetValue(CurrentPathProperty) as string[]; }
set { SetValue(CurrentPathProperty, value); }
}
What would be the correct way to bind to the property in XAML (display the bread crumbs and update CurrentPath when one of them is tapped)? I've tried googling ListViews and x:Array but don't see a straightforward way to do this. I know I need a PropertyChanged event handler but it's not clear to me what needs to happen on the XAML side or what the handler would look like.

If you want your control to update itself when items are added to your array, you should use something implementing INotifyCollectionChanged, like ObservableCollection instead of an array.
With that as source, you can bind it to the ItemSource of a ListView; the ListView being the only Xamarin.Forms controls that supports DataTemplating out of the box.
Anything more complex require defining your own control, inheriting e.g. from StackLayout or any other Layout, and handle creating a Label for each of the string added to your array.

Related

Extend an external object with an IsSelected Property for MVVM?

I have to create a small WPF/MVVM based GUI that shows the user a list of objects that I get from an external library. The user cannot directly edit those objects, but only select them for further usage.
At first I though I could directly use the given objects in a regular collection as I did not see any need for an INotifyPropertyChanged implementation, but then I noticed that I would need an IsSelected property so that the view model would know which objects are selected by the user and furthermore there is also one case where I have to select specific objects from the view model. This means I have to somehow add the said IsSelected property to make this scenario work in MVVM.
What options do I have?
Do I have to write a wrapper class that inherits from the external class and only extends it by the said IsSelected Property? This would mean I would also have to convert the list of objects that I get from the external library before I can use them.
Or is there maybe a more convenient way to extend the external object so that I can handle the selection in an MVVM based way?
You can define a collection of the selected objects on your viewmodel, like:
public class YourViewModel
{
public List<Thing> SelectedThings { get; } = new List<Thing>();
}
Because the SelectedItems property of the built-in WPF ListBox is not a DependencyProperty so it cannot be bound, you can manage your collection with a simple event handler like
<ListBox SelectionChanged="ListBox_SelectionChanged" />
in codebehind:
private YourViewModel vm;
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.AddedItems)
{
vm.SelectedThings.Add(item);
}
foreach (var item in e.RemovedItems)
{
vm.SelectedThings.Remove(item);
}
}
If you want to set the selected items from the view model, too, I have found a good solution instead of event handler here: https://www.tyrrrz.me/Blog/WPF-ListBox-SelectedItems-TwoWay-binding

Property in View must match identical property in ViewModel

I am creating a user control (a textbox that only accepts integers). The control has to have properties to specify max/min values and whether to allow negative values etc.). I am using MVVM, in my view I have public properties e.g.
const string EXAMPLE = "Example";
string example;
public string Example
{
get { return example; }
set
{
if (value == example) return;
example = value;
OnPropertyChanged(EXAMPLE);
}
}
These properties are in my View so that someone using the control will be able to easily set them. In my ViewModel I have an identical property, I need these properties to be bound together so that they and their backing fields always have the same value. I hate the code repetition too.
To be honest the whole approach feels wrong and usually that is a good indication that I am approaching the whole thing from the wrong direction or misunderstanding something fundamental.
I have used WPF before but this is a first attempt at a custom control.
The first thing I want to make sure is that you're truly trying to make a CustomControl and not a UserControl. I believe this question basically is the same as yours except worded differently.
A UserControl lends itself to the MVVM pattern way more readily than a CustomControl because you would have a .xaml (and .xaml.cs) file along with a .cs file to serve as the ViewModel. On the other hand, a CustomControl is never done with MVVM, as the visual appearance (view) is defined and overridable via a ControlTemplate.
Since you said you have a View and ViewModel, let's think about how you would achieve the behavior you want with your textbox. Your textbox will have to validate and reject user input outside the range of values you desire. This means your View code-behind has to have properties and logic that control the restrictions in the input values of the textbox defined in your View. You have already violated MVVM here.
When you said you have a View, that makes me think you're writing a UserControl. But your requirements (a custom behavior for textbox) suggest that you really need a CustomControl, for which you do not use MVVM.
If you agree that you need a CustomControl, here's a quick and dirty example:
public class RestrictedTextBox : TextBox
{
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(int), typeof(RestrictedTextBox), new PropertyMetadata(int.MaxValue));
public RestrictedTextBox()
{
PreviewTextInput += RestrictedTextBox_PreviewTextInput;
}
public int MaxValue
{
get
{
return (int)GetValue(MaxValueProperty);
}
set
{
SetValue(MaxValueProperty, value);
}
}
private void RestrictedTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
int inputDigits;
RestrictedTextBox box = sender as RestrictedTextBox;
if (box != null)
{
if (!e.Text.All(Char.IsDigit))
{
// Stops the text from being handled
e.Handled = true;
}
else if (int.TryParse(box.Text + e.Text, out inputDigits))
{
if (inputDigits > MaxValue)
e.Handled = true;
}
}
}
}
XAML Usage:
<local:RestrictedTextBox MaxValue="100"></local:RestrictedTextBox>
There is no MVVM in a Custom Control.
Any Control is only in the View layer. So what you need to do is expose relevant DP to a consumer that you have no knowledge of.
In the Custom Control you need to define your Control behavior, how it reacts to change in DP value and what should be available to a consumer. In the default Template you define how you want to display this Control.
The consumer may want to set or get some dp values so he'll have to bind your Custom Control'dp to a property in his ViewModel but that's up to him.

WPF gets "old" value when data in ListView change

I have the TreeView with objects. When I select item of these tree, than other control - ListView displays as its items properties of selected object. I want to save values of properties when in TreeView selection is change to other object.
So, is there a good way in WPF to gets values of "just before changing" items in ListView control? My idea for now is to override the PreviewMouseDown to check if user click tree node. By god way I mean better than mine. Maybe something in ListView template?
Indication that there is no need to change my idea with the PreviewMouseDown will be also good answer.
Could you please provide the relevant code snippets? I try to answer your question, but I'm not sure I understood it correctly.
If you bind the SelectedItem of you TreeView to a property (a.e. using MVVM pattern), you can save the values before actually setting the item.
Doing so in the setter is not so good though, because it becomes quite large then. I would have a setter like this:
private Foo bar;
public Foo Bar
{
get { return bar; }
set
{
OnPropertyChanging("Bar");
bar=value;
OnPropertyChanged("Bar");
}
}
Then you can listen to your own PropertyChanging events and do your stuff there:
private void this_PropertyChanging(object param, PropertyChangingEventArgs e)
{
switch(e.PropertyName)
{
case "Bar":
//Do you stuff
break,
}
}

Hair loss and MVVM user controls

I have a user control written in C# & WPF using the MVVM pattern.
All I want to do is have a property in the bound ViewModel exposed to outside of the control. I want to be able to bind to it and I want any changes to the property to be picked up by anything outside the control that is bound to the exposed value.
This sounds simple, but its making me pull out my hair (and there is not much of that left).
I have a dependency property in the user control. The ViewModel has the property implementing the INotifyPropertyChanged interface and is calling the PropertyChanged event correctly.
Some questions:
1) How do I pick up the changes to the ViewModel Property and tie it to the Dependency Property without breaking the MVVM separation? So far the only way I've managed to do this is to assign the ViewModels PropertyChanged Event in the Controls code behind, which is definitely not MVVM.
2) Using the above fudge, I can get the Dependency property to kick off its PropertyChangedCallback, but anything bound to it outside the control does not pick up the change.
There has to be a simple way to do all of this. Note that I've not posted any code here - I'm hoping not to influence the answers with my existing code. Also, you'd probably all laugh at it anyway...
Rob
OK, to clarify - code examples:
usercontrol code behind:
public static DependencyProperty NewRepositoryRunProperty = DependencyProperty.Register("NewRepositoryRun", typeof(int?), typeof(GroupTree),
new FrameworkPropertyMetadata( null, new PropertyChangedCallback(OnNewRepositoryRunChanged)));
public int? NewRepositoryRun
{
get { return (int?)GetValue(NewRepositoryRunProperty); }
set
{
SetValue(NewRepositoryRunProperty, value);
}
}
private static void OnNewRepositoryRunChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != e.NewValue)
{
}
}
public GroupTree()
{
InitializeComponent();
GroupTreeVM vm = new GroupTreeVM();
this.DataContext = vm;
}
Viewmodel (GroupTreeVM.cs)
private int? _NewRepositoryRun;
public int? NewRepositoryRun
{
get
{
return _NewRepositoryRun;
}
set
{
_NewRepositoryRun = value;
NotifyPropertyChanged();
}
}
And now for my weekly "don't do that" answer...
Creating a ViewModel for your UserControl is a code smell.
You're experiencing this issue because of that smell, and it should be an indication that you're doing something wrong.
The solution is to ditch the VM built for the UserControl. If it contains business logic, it should be moved to an appropriate location in another ViewModel.
You should think of a UserControl as nothing more than a more complex control. Does the TextBox have its own ViewModel? No. You bind your VM's property to the Text property of the control, and the control shows your text in its UI.
Think of UserControls in MVVM like this--For each model, you have a UserControl, and it is designed to present the data in that model to the user. You can use it anywhere you want to show the user that model. Does it need a button? Expose an ICommand property on your UserControl and let your business logic bind to it. Does your business logic need to know something going on inside? Add a routed event.
Normally, in WPF, if you find yourself asking why it hurts to do something, it's because you shouldn't do it.
Perhaps I've misunderstood, but it seems like you're trying to use binding in the code behind?
public MyUserControl()
{
InitializeComponent();
// Set your datacontext.
var binding = new Binding("SomeVMProperty");
binding.Source = this.DataContext;
SetBinding(MyDependencyProperty, binding);
}

Multiple ComboBoxEdit controls with same items

I am working on a user control which contains a SpinEdit (numeric up-down) control and a ComboBoxEdit. The option selected in the combo-box provides a factor by which number in the SpinEdit is multiplied. At the moment, I have something like this:
public class MyUserControl : DevExpress.XtraEditors.XtraUserControl
{
private static List<String> listItems;
static MyUserControl() // Populate the list of options with the default options
{
listItems = new List<String?();
listItems.Add("Option1");
listItems.Add("Option2");
listItems.Add("Option3");
}
public MyUserControl()
{
InitializeComponent();
// Add default options to the combo box
foreach (String item in listItems)
{
this.cboBox.Properties.Items.Add(item);
}
}
}
That works fine (note that the above is simplified, in reality the static list is a static Dictionary which maps the strings to the multiplication factor) except that I want to allow the user to add custom options to listItems and have these appear on every instance of this user control in my application. That is why listItems is static, as my hope was to do this.cboBox.Properties.Items = listItems; so that any additions to listItems would appear on every control. However, the Items property is read-only so I cannot do that.
How can I go about ensuring every instance of my user control has the same set of options, even if these are changed? Having the static members fire an event when the user changes the option list might do the trick, but that seems a bit overkill for something that looks as simple as this. Does anyone have any other ideas?
In your case is better to use LookUpEdit control instead of ComboBoxEdit. Just make some adjustments to it:
lookUpEdit1.Properties.ShowHeader = false;
You can use your listItems in that way:
lookUpEdit1.Properties.DataSource = listItems;
But there is some problem with Dictionary as DataSource. For DataSource you must use an collection that implements IList, ITypedList or IBindingList interface. So, you can convert your Dictionary to List or you can use this trick:
private static Dictionary<int, string> items;
//...
lookUpEdit1.QueryPopUp += lookUpEdit_QueryPopUp;
lookUpEdit2.QueryPopUp += lookUpEdit_QueryPopUp;
//...
private void lookUpEdit_QueryPopUp(object sender, CancelEventArgs e)
{
var lookUpEdit = (LookUpEdit)sender;
lookUpEdit.Properties.DataSource = null;
lookUpEdit.Properties.DataSource = items;
}
But I think is better to use List instead of Dictionary.
I don't know about the DevExpress controls (you might want to ask on their support forums), but in classic Windows comboboxes, the list is maintained inside the control. The Items property is a .Net wrapper that makes the internal structure easier to work with. So, it's not possible to assign the same data structure to each combobox. They have their own copy.
Your idea to have a publish/subscribe mechanism to what are now static lists seems like a reasonable solution.
If it were me, instead of having static stuff on the control class, I'd make that master list container a separate class and have a property on the control that takes an instance of that class. It'd make your overall architecture a bit more flexible and testable.

Categories