Changing the View for a Viewmodel

Changing the View for a ViewModel

Less words more code.
As far as you said, you have the class SampleViewModel. I added the property Title for demonstration and ViewType for identifying the correct view:

public enum ItemViewType { View1, View2 };

public class SampleViewModel
{
public string Title { get; set; }
public ItemViewType ViewType { get; set; }
}

The DataTemplateSelector for two views depending on the ViewType property:

class ItemViewTemplateSelector : DataTemplateSelector
{
public DataTemplate View1Template { get; set; }
public DataTemplate View2Template { get; set; }

public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var vm = item as SampleViewModel;
if (vm == null)
return null;

switch (vm.ViewType)
{
case ItemViewType.View1:
return View1Template;
case ItemViewType.View2:
return View2Template;
}

return null;
}
}

Xaml code:

<Window.Resources>
<DataTemplate x:Key="view1Template">
<TextBlock Text="{Binding Title}" Foreground="Red"/>
</DataTemplate>
<DataTemplate x:Key="view2Template">
<TextBox Text="{Binding Title}" />
</DataTemplate>
<local:ItemViewTemplateSelector x:Key="viewTemplateSelector"
View1Template="{StaticResource view1Template}"
View2Template="{StaticResource view2Template}"/>
</Window.Resources>

<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>

<StackPanel>
<Button Content="ChangeView" HorizontalAlignment="Center" Command="{Binding SwitchViewCommand}"/>
<ContentControl Content="{Binding ItemViewModel}" ContentTemplateSelector="{StaticResource viewTemplateSelector}"/>
</StackPanel>

The main part is in the class MainViewModel where I've put the logic for switching views:

public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
this.ItemViewModel = new SampleViewModel { Title = "Some title", ViewType = ItemViewType.View1 };

this.SwitchViewCommand = new RelayCommand(() =>
{
this.ItemViewModel.ViewType = this.ItemViewModel.ViewType == ItemViewType.View1
? ItemViewType.View2
: ItemViewType.View1;
//The magic senquence of actions which forces a contentcontrol to change the content template
var copy = this.ItemViewModel;
this.ItemViewModel = null;
this.ItemViewModel = copy;
});
}

public RelayCommand SwitchViewCommand { get; set; }

private SampleViewModel itemViewModel;

public SampleViewModel ItemViewModel
{
get { return itemViewModel; }
set
{
itemViewModel = value;
RaisePropertyChanged("ItemViewModel");
}
}
}

The SwitchViewCommand can be any type of command, I use the command from the mvvmlight library.

Inside the handler of the command I change the type of viewmodel and update the property ItemViewModel in a tricky way because a ContentControl refreshes a view only if to change the Content property, and this property will not be changed unless you set a reference to different object.

I mean, even the code this.ItemViewModel = this.itemViewModel will not change the view.
It's strange, but the workaround doesn't require much work.

Change view via ViewModel

Thanks to Gorky's respond I figured out how to do that.

In Fragment I created observer

sharedViewModel.changeView.observe(viewLifecycleOwner, Observer<Boolean> { hasFinished ->
if (hasFinished) nextFragment()
})

I created changeView variable in ViewModel. When

 var changeView = MutableLiveData<Boolean>()

change to true, observer call function.

source:
https://developer.android.com/codelabs/kotlin-android-training-live-data#6

How to change something in View by ViewModel

Your question seems a little un-thought through... by changing a property in the view model, I can make just about anything happen in the view, through the use of IValueConverters, DataTriggers, Attached Properties, etc. For example, you don't need to use either a DependencyProperty or an Attached Property to focus a UI element. You can do that with a plain bool property in the view model and a DataTrigger like this:

<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding PropertyName}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsFocused}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding
RelativeSource={RelativeSource Self}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

So really, you seem to be asking what is possible in WPF and I don't think that that is a reasonably scoped (or on-topic) question to ask here. To find that out, you'd be better off reading through the Walkthrough: Getting Started with WPF page on MSDN.

However, I think that you might actually be asking How to change something in View by ViewModel [specifically when using MVVM]. In that case, I can think of another used element: the humble delegate, which enables us to pass values between related view models, or in your case, from the view model to the view.

Using this mechanism, we can literally [indirectly] call UI methods in views from the view model, so as I said earlier... we can do just about anything - MVVM does not stop us from using anything that WPF provides.

Incidentally, why are you using DependencyPropertys in your view model? You shouldn't have any declared in your view model, because they are for UI elements... in view models, you can just implement the INotifyPropertyChanged interface and use plain old CLR properties with exactly the same functionality, but much less complexity.


UPDATE >>>

As mentioned, the DependencyProperty is a UI class, meant for UI controls, such as a UserControl. They are total overkill for use in view models, because as mentioned, we can use plain CLR properties there instead. If you look at the DependencyProperty Class page on MSDN, you'll be able to see how many hundreds of public members that it has... why pay for all of these (in RAM) when you're not using any of them?

The INotifyPropertyChanged interface doesn't add anything more than a DependencyProperty, but instead offers the same property notification access, but for a greatly reduced cost (no hundreds of unused properties). I can tell by the way that you said that all of your view models extend the DependencyObject class, that you have misunderstood the famous error below:

A Binding can only be set on a DependencyProperty of a DependencyObject

I confess that it is a misleading error and does confuse new WPF users. So, my advice to you is for you to implement ordinary CLR type properties in your view model and to not extend the DependencyObject class... you will then also be able to remove your UI related dll usings. Furthermore, when using CLR properties, you will need to correctly implement the INotifyPropertyChanged interface in order to 'plug them into' the WPF property change notification framework.

Now, you asked how to use delegates to pass data from the view model to the view... unfortunately, that is another whole question on its own and I have just about run out of time on this question. So instead of repeating the whole story again, I'd prefer to suggest that you read my answers to the Passing parameters between viewmodels and How to call functions in a main view model from other view models? questions here on Stack Overflow for explanations and code examples.

With all due respect, I'd appreciate it if you don't leave further comments asking for more information on this matter as this answer is really long enough now. If you do have any further questions, please ask a new question and provide as much code/information as possible.

Changing View from ViewModel in View-Base WPF Application

In MVVM Light there is a feature called Messenger. You could use this to communicate between ViewModels.

Here is an answer with an example of that: MVVM Light Messenger - Sending and Registering Objects

SwiftUI: Change view @State property from ViewModel

You can directly bind to the @Published property with just:

$viewModel.startTime

Which will be of type Binding<Date>, even though we declare it as just a @Published property on BookTimeViewModel.

Example code:

class BookTimeViewModel: ObservableObject {
@Published var startTime: Date = .now
}
struct BookTimeView: View {
@ObservedObject var viewModel: BookTimeViewModel

var body: some View {
DatePicker("pick time", selection: $viewModel.startTime, displayedComponents: .hourAndMinute)
}
}

Similar to my answer here.

Save Changes of View in Viewmodel?

You can make the Mode=TwoWay and use UpdateSourceTrigger=Explicit. Refer below code.

 <StackPanel>
<TextBox Name="tb_1" Text="HelloWorld!"/>
<TextBox Name="tb_2" Text="{Binding Path=Text, ElementName=tb_1, Mode=TwoWay,UpdateSourceTrigger=Explicit}"/>
<Button Content="Update Source" Click="Button_Click"/>
</StackPanel>

Changing ViewModel Object does not reflect the changes to the View

So apparently, I had to do two things to make it work.

Since I was binding the User Control to itself like this...

this.DataContext = this;

I had to implement INotifyPropertyChange on the User Control class itself and implement the PropertyChanged call on the CurrentViewModel property, like this:

private PageSetupEditorViewModel _currentViewModel;
public PageSetupEditorViewModel CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
OnPropertyChanged(nameof(CurrentViewModel));
}
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

But that did not entirely updated the view as I was using a collection to generate tabs in the TabControl.

I had to refresh the view for TabComtrol.Items using this on the selection of ComboBox's selection change event, like this:

tcOrientation.Items.Refresh();

This solved the problem for me :)

Change the view element property based on ViewModel property change

Change the style triggers to:

<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsBlueColor}" Value="True">
<Setter Property="Foreground" Value="RoyalBlue"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsBlueColor}" Value="False">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
</Style.Triggers>

Check implementation of INotifyPropertyChanged in ViewModel.

this works in a sample for me.

Sample :

class LabelViewModel:INPC
{
public event PropertyChangedEventHandler PropertyChanged=new delegate{};
bool _isBlueColor;
public bool IsBlueColor
{
get
{
return _isBlueColor;
}
set
{
_isBlueColor=value;
OnPropertyChange();
}
}
public OnPropertyChange([CallerMemberName] string propname="")
{
PropertyChanged.Invoke(this,ew PropertyChangedEventArgs(propname));
}

}

Create an instance of LabelViewModel(labelVM) & assign to label1.DataContext=labelVM;



Related Topics



Leave a reply



Submit