ReactiveUI WhenActivated and Virtual scrolling - wpf

This ReactiveUserControl is inside a ListBoxItem. The ListBox is using virtualization. If I register the ShowDialogAsync on WhenActivated (*1) only the firsts UserControls are bound because virtualization recycles controls. Then, I should to bind using OnDataContextEndUpdate (*2). But this is not veri "reactive" way.
public class CentreRowUserCtrl : ReactiveUserControl<CentreRowViewModel>
{
public CentreRowUserCtrl()
{
InitializeComponent();
// (*1):
// this.WhenActivated(d =>
// d(ViewModel!.ShowDialog.RegisterHandler(ShowDialogAsync)));
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override void OnDataContextEndUpdate()
{
base.OnDataContextBeginUpdate();
// (*2):
ViewModel?.ShowDialog.RegisterHandler(ShowDialogAsync);
}
How can I register with ReactiveUI in a virtualization context?

Answered here: https://github.com/AvaloniaUI/Avalonia/discussions/6427#discussioncomment-1191558
As stated in WhenActivated documentation, the WhenActivated ReactiveUI feature is a way to track disposables. There is no guarantee that the ViewModel property (the DataContext property of an IViewFor) won't be null when the control is activated, and that the view model won't change since control activation. That's why the IViewFor.ViewModel property is marked as nullable in ReactiveUI. The general recommendation is to never access the ViewModel property directly inside a WhenActivated block. Always use a call to WhenAnyValue in a similar fashion to this code snippet:
this.WhenActivated(disposables => {
this.WhenAnyValue(x => x.ViewModel)
.Subscribe(vm => { /* Here you can access the view model safely. */ })
.DisposeWith(disposables);
});

Related

Click on CheckBox Binding

I have Car.xaml:
<CheckBox Name="carA" IsChecked="{Binding CarACheck, Mode=TwoWay, FallbackValue=true}" />
Then in Car.xaml.cs:
public Car()
{
//...
DataContext = this;
}
public bool CarACheck
{
get => carA.IsChecked;
set => carA.IsChecked = value;
}
When I run it and click on CheckBox, the app crashes with the error below:
set => carA.IsChecked = value;
System.StackOverflowException
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
Cause of Error
The reason is that your setter is called over and over.
The CheckBox is clicked.
The IsChecked property of the CheckBox is set.
The binding sets value on the bound property CarACheck.
The setter of CarACheck sets the IsChecked property of the CheckBox.
Go to 3.
In the long run this causes the stack to overflow and the application crashes.
An MVVM Solution
It seems that you are trying to build a view that displays data about cars. What I see from your sample is that you mix your data and most likely business logic with the user interface code. You should not do that, because it harms code quality and maintainability in the long run. A better and sustainable approach is to separate your view from data and business logic. This is what the MVVM pattern is about.
In your sample code, we have a view, which we would call CarView. This is where the CheckBox is defined along with the rest of the user interface to represent a car. The data is exposed to the view through a separate view model type called CarViewModel. This view model contains properties that are bound from the view.
When using simple properties, regardless of having a backing field or not, the bindings are not able to determine changes to properties from the view model side. This is why you have to implement the INotifyPropertyChanged interface, which provides an event for this purpose. The event is raised, whenever a property is changed. Usually this is in done in the setter.
public class CarViewModel : INotifyPropertyChanged
{
private bool _carACheck;
public bool CarACheck
{
get => _carACheck;
set
{
if (_carACheck == value)
return;
_carACheck = value;
OnPropertyChanged(nameof(_carACheck));
}
}
// ...other code, maybe even setting CarACheck.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the view, simply assign an instance of this view model to the DataContext in XAML or code-behind, e.g.:
public Car()
{
// ...other code.
DataContext = new CarViewModel();
}
In your view, you do not need a name for the CheckBox, as there is no explicit reference to it. The Mode=TwoWay declaration can be removed, too, because the IsChecked property binds two-way by default.
<CheckBox IsChecked="{Binding CarACheck, FallbackValue=true}"/>
Remarks about your original code: I hope that you can see the benefits of the MVVM pattern. Of course, after reviewing this solution, you could simply add a backing field and INotifyPropertyChanged to your current code to make it work, too, but the lesson to learn here is that this separation is valuable and worth investing although it might seem more verbose. See also:
Data binding overview (WPF .NET)
How to: Implement Property Change Notification

Triggering an action of a UIElement from a ViewModel

I've implemented Wiesław Šoltés' awesome ZoomBorder, but I'm struggling a bit with WPF and MVVM. For the questions' completeness sake, ZoomBorder is a UIElement that inherits from Border and gives the user the possibility of zooming and panning the content of the inherited border. It also has the ability to reset the zoom and panning.
I'd like to make ZoomBorder react to an event being published by certain view model, so that when that event is published, ZoomBorder resets the zoom. In my implementation, the ZoomBorder's DataContext is a ContentViewModel, which has an IEventAggregator (Prism.Events) injected via Autofac. Ideally, I would have liked to inject the event aggregator directly into ZoomBorder, so that it can subscribe to the event, but I can't because the constructor needs to be parameterless.
So ContentViewModel has to subscribe to the event, but how would I invoke ZoomBorder's Reset method from ContentViewModel? I understand I'd be transgressing MVVM, but I don't know how else to do it. I thought about making ZoomBorder expose a Command via a dependency property, but then the Reset code would have to be on the view model, which it can't.
You can use the ServiceLocator inside of views or controls to resolve types from the container.
public ZoomBorder()
{
_eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
}
If you use AutoFac and the event aggregator without Prism, you can use the package Autofac.Extras.CommonServiceLocator and register your container to ServiceLocator.
var builder = new ContainerBuilder();
var container = builder.Build();
var csl = new AutofacServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => csl);
I would use binding.
Add a dependency property to Zoomborder.
public bool? ResetZoom
{
get
{
return (bool?)GetValue(ResetZoomProperty);
}
set
{
SetValue(ResetZoomProperty, value);
}
}
public static readonly DependencyProperty ResetZoomProperty =
DependencyProperty.Register("ResetZoom",
typeof(bool?),
typeof(CloseMe),
new PropertyMetadata(null, new PropertyChangedCallback(ResetZoomChanged)));
private static void ResetZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ZoomBorder zb = (ZoomBorder)d;
zb.Reset();
zb.SetCurrentValue(ResetZoomProperty, false);
}
You can then bind that to a public bool property in your ContentViewModel.
When set to true, the border will reset.
This video shows how to create abstractions from Views/UI components and call methods on them from a VM using an interface. Don't let the title fool you. This look like it will fit this scenario perfectly
"How to Close Windows from a ViewModel in C#"
https://youtu.be/U7Qclpe2joo
Instead of calling the Close method on a window, you can adapt it to call your Reset method on your control from the VM.

MVVM pattern violation: MediaElement.Play()

I understand that ViewModel shouldn't have any knowledge of View, but how can I call MediaElement.Play() method from ViewModel, other than having a reference to View (or directly to MediaElement) in ViewModel?
Other (linked) question: how can I manage View's controls visibility from ViewModel without violating MVVM pattern?
1) Do not call Play() from the view model. Raise an event in the view model instead (for instance PlayRequested) and listen to this event in the view:
view model:
public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
this.PlayRequested(this, EventArgs.Empty);
}
view:
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
this.myMediaElement.Play();
};
2) You can expose in the view model a public boolean property, and bind the Visibility property of your controls to this property. As Visibility is of type Visibility and not bool, you'll have to use a converter.
You can find a basic implementation of such a converter here.
This related question might help you too.
For all the late-comers,
There are many ways to achieve the same result and it really depends on how you would like to implement yours, as long as your code is not difficult to maintain, I do believe it's ok to break the MVVM pattern under certain cases.
But having said that, I also believe there is always way to do this within the pattern, and the following is one of them just in case if anyone would like to know what other alternatives are available.
The Tasks:
we don't want to have direct reference from the ViewModel to any UI elements, i.e. the the MediaElement and the View itself.
we want to use Command to do the magic here
The Solution:
In short, we are going to introduce an interface between the View and the ViewModel to break the dependecy, and the View will be implementing the interface and be responsible for the direct controlling of the MediaElement while leaving the ViewModel talking only to the interface, which can be swapped with other implementation for testing purposes if needed, and here comes the long version:
Introduce an interface called IMediaService as below:
public interface IMediaService
{
void Play();
void Pause();
void Stop();
void Rewind();
void FastForward();
}
Implement the IMediaService in the View:
public partial class DemoView : UserControl, IMediaService
{
public DemoView()
{
InitializeComponent();
}
void IMediaService.FastForward()
{
this.MediaPlayer.Position += TimeSpan.FromSeconds(10);
}
void IMediaService.Pause()
{
this.MediaPlayer.Pause();
}
void IMediaService.Play()
{
this.MediaPlayer.Play();
}
void IMediaService.Rewind()
{
this.MediaPlayer.Position -= TimeSpan.FromSeconds(10);
}
void IMediaService.Stop()
{
this.MediaPlayer.Stop();
}
}
we then do few things in the DemoView.XAML:
Give the MediaElement a name so the code behind can access it like above:
<MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
Give the view a name so we can pass it as a parameter, and
import the interactivity namespace for later use (some default namespaces are omitted for simplicity reason):
<UserControl x:Class="Test.DemoView"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="MediaService">
Hookup the Loaded event through Trigger to pass the view itself to the view model through a Command
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction>
</ia:EventTrigger>
</ia:Interaction.Triggers>
last but not least, we need to hookup the media controls through Commands:
<Button Command="{Binding PlayCommand}" Content="Play"></Button>
<Button Command="{Binding PauseCommand}" Content="Pause"></Button>
<Button Command="{Binding StopCommand}" Content="Stop"></Button>
<Button Command="{Binding RewindCommand}" Content="Rewind"></Button>
<Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>
We now can catch everything in the ViewModel (I'm using prism's DelegateCommand here):
public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest
{
public IMediaService {get; private set;}
private DelegateCommand<IMediaService> loadedCommand;
public DelegateCommand<IMediaService> LoadedCommand
{
get
{
if (this.loadedCommand == null)
{
this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) =>
{
this.MediaService = mediaService;
});
}
return loadedCommand;
}
}
private DelegateCommand playCommand;
public DelegateCommand PlayCommand
{
get
{
if (this.playCommand == null)
{
this.playCommand = new DelegateCommand(() =>
{
this.MediaService.Play();
});
}
return playCommand;
}
}
.
. // other commands are not listed, but you get the idea
.
}
Side note: I use Prism's Auto Wiring feature to link up the View and ViewModel. So at the View's code behind file there is no DataContext assignment code, and I prefer to keep it that way, and hence I chose to use purely Commands to achieve this result.
I use media element to play sounds in UI whenever an event occurs in the application. The view model handling this, was created with a Source property of type Uri (with notify property changed, but you already know you need that to notify UI).
All you have to do whenever source changes (and this is up to you), is to set the source property to null (this is why Source property should be Uri and not string, MediaElement will naturally throw exception, NotSupportedException I think), then set it to whatever URI you want.
Probably, the most important aspect of this tip is that you have to set MediaElement's property LoadedBehaviour to Play in XAML of your view. Hopefully no code behind is needed for what you want to achieve.
The trick is extremely simple so I won't post a complete example. The view model's play function should look like this:
private void PlaySomething(string fileUri)
{
if (string.IsNullOrWhiteSpace(fileUri))
return;
// HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI.
this.Source = null;
this.Source = new Uri(fileUri);
}
Here is the Source property, nothing special about it:
#region Source property
/// <summary>
/// Stores Source value.
/// </summary>
private Uri _Source = null;
/// <summary>
/// Gets or sets file URI to play.
/// </summary>
public Uri Source
{
get { return this._Source; }
private set
{
if (this._Source != value)
{
this._Source = value;
this.RaisePropertyChanged("Source");
}
}
}
#endregion Source property
As for Visibility, and stuff like this, you can use converters (e.g. from bool to visibility, which you can find on CodePlex for WPF, SL, WP7,8) and bind your control's property to that of the view model's (e.g. IsVisible). This way, you control parts of you view's aspect. Or you can just have Visibility property typed System.Windows.Visibility on your view model (I don't see any pattern breach here). Really, it's not that uncommon.
Good luck,
Andrei
P.S. I have to mention that .NET 4.5 is the version where I tested this, but I think it should work on other versions as well.

WPF: DependencyProperty of custom control fails when using several instances of the control

I've built a custom control in WPF that inherits from ListBox. In this I have implementet my own property that is a BindingList. To make this property bindable I've implemeneted it as a DependencyProperty:
public BindingList<CheckableListItem> CheckedItems
{
get
{
return (BindingList<CheckableListItem>)GetValue(MultiComboBox.CheckedItemsProperty);
}
set
{
SetValue(MultiComboBox.CheckedItemsProperty, value);
}
}
public static readonly DependencyProperty CheckedItemsProperty;
I register this DependencyProperty in a static constructor inside my custom control:
CheckedItemsProperty = DependencyProperty.Register("CheckedItems",
typeof(BindingList<CheckableListItem>),
typeof(MultiComboBox),
new FrameworkPropertyMetadata(new BindingList<CheckableListItem>()));
(MultiComboBox is the name of my custom control. CheckableListItem is a simple class I've written just for this purpose).
This BindingList is then updated inside the custom control (never outside) as the user interacts with it.
When I use my custom control in XAML I bind to the CheckItems property with the mode "OneWayToSource". I'm using the MVVM pattern and the property in the ViewModel that I'm binding to is also a BindingList. The ViewModel never affects this list, it just reacts at the changes that the custom control make to the list. The property in the ViewModel looks like this:
private BindingList<CheckableListItem> _selectedItems;
public BindingList<CheckableListItem> SelectedItems
{
get
{
return _selectedItems;
}
set
{
if (value != _selectedItems)
{
if (_selectedItems != null)
{
_selectedItems.ListChanged -= SelectedItemsChanged;
}
_selectedItems = value;
if (_selectedItems != null)
{
_selectedItems.ListChanged += SelectedItemsChanged;
}
OnPropertyChanged("SelectedItems");
}
}
}
As you can see I'm listening to changes made to the list (these changes always occur inside my custom control), and in the "SelectedItemsChanged"-method I update my Model accordingly.
Now...this works great when I have one of these controls in my View. However, if I put two (or more) of them in the same View strange things start to happen. This will of course mean that I'll have two lists with selected items in my ViewModel. But if do something in the View that changes one of the lists, both lists are affected! That is, the event handlers for the event ListChanged is triggered for both list if changes are made to any one of them!
Does anyone recognize this problem and/or have a solution to it? What is wrong with my implementation?
My first though is that the DependencyProperty is static. Normally that means shared between all instances. But I guess DependencyProperties work in some other "magical" way so that might not be the problem.
Any tips or hints is appreciated!
I had a similar problem with a collection-type dependency property. My solution was taken from the MSDN article on Collection-Type Dependency Properties. It was adding the following line
SetValue(OperatorsPropertyKey, new List<ListBoxItem>()); //replace key and type
in the constructor of my control because it seems that a collection-type dependency property constructor is being called only once no matter how many instances your control containing this collection has (static eh).
This sounds like you bound both/all the Views to the same ViewModel. That would explain that changes to one cause changes in the other.

MvvM with custom View-Elements. Data Binding problems

As a newbie in Data-Binding, I don't know what I am doing wrong.
I have some GUI elements defined in XAML, and I have data-binded them with appropriate ViewModels. So far so good.
I also have some custom elements (geometrical shapes) that I place inside a Canvas (which Canvas I place inside the mainwindow through a user-control). I derived these entities from FrameworkElement, to have support for data-binding.
So what I have done is to register some DependencyProperties and set the bindings to one of the existing ViewModels, as it seemed to me logical.
Now the DependencyProperties of these custom classes, display some strange behaviour.
i) When I keep the focus only on the Views (controls) that use the same ViewModel with the custom elements, the properties update normally. If I click everywhere else, the bindings break.
ii) Sometimes, the Callback wasn't called although the property was changing.
iii) When the StartupURI in App.xaml was the MainWindow, if I declared the ProfileV as a field (no matter where it was instantiated), the databinding mechanism worked in the way of (i). If it was declared inside the constructor, the mechanism didn't worked.
What I am doing wrong, and what crucial thing i misunderstand about databinding ??
class ProfileV : FrameworkElement, IGraphicalElement
{
public int SelectedTab
{
get { return (int)GetValue(SelectedTabProperty); }
set { SetValue(SelectedTabProperty, value); }
}
public static readonly DependencyProperty SelectedTabProperty =
DependencyProperty.Register("SelectedTab", typeof(int), typeof(ProfileV),
new PropertyMetadata(new PropertyChangedCallback(CallBack)));
public ProfileV(GeneralExecutionVM VM,CanvasV canvasV)
{
DataContext = VM;
BindingOperations.SetBinding(this, SelectedTabProperty, new Binding("SelectedTab"));
}
public static void CallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}
Which binds with this
public class GeneralExecutionVM : ObservableObject
{
private int _SelectedTab;
public int SelectedTab
{
get { return _SelectedTab; }
set
{
if (_SelectedTab == value) return;
_SelectedTab = value;
base.RaisePropertyChanged("SelectedTab");
}
}
}
(Observable Object, is the base class from the MVVM Foundation, of Josh Smith.)
ANSWERED
OK i found it. The misconception here is about the DataContext.
Be careful when and where you set it, against setting an explicit source object. I misused it here, and caused a small chaotic situation.

Resources