How can I change the VisualState of an element from the Prism ViewModel?
Is there a Bindable property I can use?
Thanks!
In order to change the visual state of an element, you can do it from the Code-Behind of a page, like this:
VisualStateManager.GoToState(yourViewElement, yourViewElementState);
However, not the best advice, but you can subscribe to changes in your viewmodel, from your code.behind and then invoke the VisualStateManager.
Example
YourPage()
{
viewModel.PropertyChanged += FooChanged;
}
public void FooChanged()
{
if (!args.PropertyName.Equals("YourProperty"))
{
VisualStateManager.GoToState(yourViewElement, yourViewElementState);
}
return;
}
Found the solution in this post, adding a Behavior to the XAML page:
https://forums.xamarin.com/discussion/127531/behavior-bindableproperty-not-update
Related
When I inherit a View from ReactiveInjectableComponentBase<T> the body of the View will be "rendered" twice. I noticed this because a #foreach loop was executed twice on activation of the view. This effect happens no matter how simple the View and the ViewModel are.
Simple reproduction repo: https://github.com/Lukzy/ReactiveUI.ExecutedTwiceBug
Bug filed on ReactiveUI GitHub: https://github.com/reactiveui/ReactiveUI/issues/3256
I used the following code to simulate the behavior.
ViewWithoutInherits: "View rendered" will be executed once.
#page "/whatever"
#using System.Diagnostics
#{
Trace.TraceInformation("View renderer");
}
ViewWithInherits: "View rendered" will be executed twice.
#page "/whatever"
#using System.Diagnostics
#inherits ReactiveInjectableComponentBase<ViewModel>
#{
Trace.TraceInformation("View renderer");
}
ViewModel
using ReactiveUI;
public class TestViewModel : ReactiveObject
{
}
Am I missing something or is this a bug? Any help or hint is highly appreciated.
UPDATE #1:
I replaced the trace code with the suggestion from Dylan Barquilla.
#code {
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Trace.TraceInformation("View renderer in FIRST RENDER");
}
Trace.TraceInformation("View renderer");
base.OnAfterRender(firstRender);
}
}
Output from ViewWithoutInherits:
View renderer in FIRST RENDER
View renderer
Output from ViewWithInherits:
View renderer in FIRST RENDER
View renderer
View renderer
UPDATE #2:
ViewWithInherits: foreach-loop will be executed twice.
#page "/whatever"
#inherits ReactiveInjectableComponentBase<ViewModel>
#foreach (var value in ViewModel.Values)
{
<p>#(value)</p>
}
ViewWithInject: foreach-loop will be executed once.
#page "/whatever"
#foreach (var value in ViewModel.Values)
{
<p>#(value)</p>
}
#code {
[Inject]
public TestViewModel ViewModel { get; set; }
}
ViewModel
using ReactiveUI;
public class TestViewModel : ReactiveObject
{
public List<string> Values{ get; set; } = new()
{
"a",
"b"
};
}
EDIT 2: after cloning the repo, to prove what I want to say.
I just added a button on both components, with a simple handler (with no real effect).
<button #onclick="#OnClickHandler">Click on me</button>
//-- CODE PART --
public string Test { get; set; } = string.Empty;
private void OnClickHandler()
{
Test = "New";
}
You will see that every time you click on the button, the Foreach loop was executed [N] times will be updated with a two increments.
It is a perfect normal behavior in Blazor. A component being re render is a normal thing, and happen A LOT of time, basically every UI interaction can force the component to re render. So it should not cause you trouble, and Blazor has some useful methods (SetParametersAsync or OnInitialized) that can be used to apply logic only once, not every time the component is rendering.
If a component is inherit of another component, it could be the parent to cause this re rendering, by apply some logic (adding fields, updating fields etc.).
EDIT: I just checked the new outputs that you get, and I think the explanation is that the library that you use update or inject something in the component, after its first rendering, for example adding some fields on it or anything. It is a classic behavior with some other Blazor libraries, so I guess it is pretty normal - but you can ask in the Github / forum of the library you use to be sure.
So it basically means that if you want your code do be executed only once, I suggest you to use the following:
#code {
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
// Do whatever you need here, it will be executed only once
}
base.OnAfterRender(firstRender); // this line is important for Blazor lifecycle
}
}
Scenario:
MainWindow has a Menu About which relates to AboutWindow.
About Meny is triggered by command:
<MenuItem Header="_About" Command="{Binding OpenAbout}"/>
OpenAbout is property like that:
private RelayCommand _openAbout;
public RelayCommand OpenAbout
{
get
{
return _openAbout ?? (_openAbout = new RelayCommand(() => Messenger.Default.Send(new NotificationMessage("ShowAboutView"))));
}
}
Notification message is registered in App.cs class as follows:
static App()
{
DispatcherHelper.Initialize();
}
public App()
{
RegisterMessenger();
}
public void RegisterMessenger()
{
Messenger.Default.Register<NotificationMessage>(this, ProcessShowAboutView);
}
private void ProcessShowAboutView(NotificationMessage message)
{
AboutWindow view = new AboutWindow();
view.Show();
}
I analysed another questions like that:
How to open a new window using MVVM Light Toolkit
WPF MVVM - How to Show a view from MainWindowViewModel upon Clicking on button
I like Messenger functionality but however I am not sure If above solution is a good one.
I would be thankful for any advise!
As depicted above, Registering messages is done in App Config.
I consider it not be a good place therefore I need to know what place would be better.
Another place to consider would be Locator
I personaly would register the messages in App.xaml.cs in the OnStartup method (WPF) and in the set up method of the unit test (dont forget to unregister everything in the tear down method).
I am using the excellent Mvvmcross and Ninja Coder for Mvvmcross for building a cross platform app. For my windows store app I have created a view and a view model using Ninja coder. I have also created a UserControl which will be referenced in the view. Hence I need to bind the same viewmodel to the User control also. I have been trying to set the Data context of the user control to the singleton instance of viewmodel. I have set the data context of the user control like below.
public sealed partial class SearchResultsGridViewControl : UserControl
{
private SearchresultsViewModel _viewModel;
public SearchResultsGridViewControl()
{
this.InitializeComponent();
_viewModel = Mvx.IocConstruct<SearchresultsViewModel>();
this.DataContext = _viewModel;
}
}
But when I refer this User Control in my main view, it throws an error in XAML saying "Object Reference not set to an instance of an object. Cannot create an instance of SearchResultsGridViewControl".
This is my viewmodel:
public class SearchresultsViewModel : BaseViewModel
{
private ISearchResultsService _searchResultsService;
public SearchresultsViewModel(ISearchResultsService searchResultsService)
{
_searchResultsService = searchResultsService;
var items = _searchResultsService.DisplaySearchResults();
SchoolDetails = new ObservableCollection<School>(items);
}
private ObservableCollection<School> _schoolDetails;
public ObservableCollection<School> SchoolDetails
{
get { return _schoolDetails; }
set
{
_schoolDetails = value;
RaisePropertyChanged(() => SchoolDetails);
}
}
public ICommand RefineCommand
{
get
{
refineCommand = refineCommand ?? new MvxCommand(FilterSearchResultsBasedOnRefine);
return refineCommand;
}
}
public void FilterSearchResultsBasedOnRefine()
{
SchoolDetails = new ObservableCollection<School>(_searchResultsService.FilterSchoolsBasedOnRefine(MidDayMeals, PlayGround, DigitalClassroom, DayBoarding, TransportationFacility));
}
}
The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated. And I am guessing its because of that error earlier in setting the data context of the user control to view model. Please let me know what could be going wrong. I have been banging my head about it for days.
I've been using MVVMCross with Windows Store fairly recently. Without looking back at my code, I'm pretty sure that the Datacontext will inherit from it's parent unless overridden.
So as long as the MvxPage that you have presented has a viewmodel, any user control that you add to it, either in XAML or in code-behind, should share the same data context. If you are looking at doing some MVVMCross data-binding from the User Control, you should probably make sure your User Control implements IMvxStoreView, and ensure that the ViewModel property is set to the value of DataContext.
Hope that help.
Cheers,
Tristan
I think your first problem "Object Reference not set to an instance of an object" is a design time only issue - because you are trying to set the viewmodel using Mvx. at design time. You can workaround this issue if you want to by using design time viewmodels and possibly also by using one of the design time helpers (see https://github.com/MvvmCross/MvvmCross/blob/v3.1/CrossCore/Cirrious.CrossCore.Wpf/Platform/MvxDesignTimeHelper.cs).
I've no idea what your second problem is "The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated" - this sounds like an issue either in your xaml or in the results returned from FilterSearchResultsBasedOnRefine. From the current level of detail, I can't see what it is. My "gut feeling" is that the problem won't be MvvmCross specific - it'll just be a general Mvvm/data-binding issue.
Our designer created a layout something like the screen above. The main idea was to create an application with only one screen, just the red part of the screen is changing (i.e. 2 textbox instead of 1 textbox) when you tap on a button. This application will be a multiplatform application and I'm using MvvmCross to create it. My question is that how can i achieve this behavior in Mvvm? My first thought was sg. like the code below, but I'm not satisfied with this solution. Do you have any better solution to this problem? Should i somehow overwrite default navigation on ShowViewModel()?
public class MainViewModel : MvxViewModel
{
private MvxViewModel _currentViewModel;
public MvxViewModel CurrentViewModel
{
get { return _currentViewModel; }
set { _currentViewModel = value; RaisePropertyChanged(() => CurrentViewModel); }
}
public MainViewModel()
{
CurrentViewModel = new DefaultViewModel();
}
public void OnButtonClick()
{
CurrentViewModel = new SecondViewModel();
}
}
public partial class MainViewModel : MvxViewController
{
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
FirstViewModel.WeakSubscribe(ViewModelPropertyChanged);
}
private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "CurrentViewModel")
{
if (Model.CurrentViewModel != null)
{
if (Model.CurrentViewModel is SecondViewModel)
{
//remove bindings
//change View
//bind new viewmodel
}
}
}
}
The alternatives for this kind of 'non-page navigation' are similar to those in MvvmCross Dialog:
You can:
Customize the MvxPresenter to allow ShowViewModel to be used
Put a special interface in the Core project and use Inversion of Control to inject the implementation from the UI project to the Core project
Use the MvxMessenger plugin and share messages between the Core and UI project which trigger this type of navigation.
Use a property with a special interface (like IInteractionRequest) on the ViewModel - that property will fire an event when the UI needs to change.
Personally, for your situation, I quite like the first of these options - intercepting ShowViewModel using a presenter.
One other alternative which I might consider is to use some kind of 'Adapter-driven' control which could very easily update it's child contents based on the CurrentViewModel property. On Android, this would be as easy as using an MvxLinearLayout with an adapter. On iOS, however, I think you'd have to write something new to do this - just because iOS doesn't really have a LinearLayout/StackPanel control.
I have added some extra functionality to the standard GWT ListBox by extending it like so:
public class FeatureListBox extends ListBox
{
public FeatureListBox()
{
}
public FeatureListBox(boolean isMultipleSelect)
{
super(isMultipleSelect);
}
public FeatureListBox(Element element)
{
super(element);
}
}
Nothing fancy here. However, the Change event is not firing now, or at least the handler (attached per below) is not getting invoked.
FeatureListBox listBox = new FeatureListBox();
listBox.addChangeHandler(new ChangeHandler()
{
public void onChange(ChangeEvent event)
{
// Do something here...
}
});
Any ideas why?
Either remove the no-argument constructor from FeatureListBox or call super() inside it, otherwise the initialization in the superclasses won't happen, which would probably result in what you're seeing.
The problem was in the way I was using my custom list box. In my application I wrap GWT Widgets around existing DOM elements on the page using the static wrap() methods of their widget classes in which the widgets get marked as attached, making them fire events. I didn't do that with my custom list box class originally, so I ended up implementing a static wrap() method similar to the one of the regular ListBox widget and using it in my code. Everything works like a charm now.