WPF MVVM: Switch from one User Control to Another on button click - c#

I've been slowly playing around with MVVM and the concept makes since. I have an application window and 2 user controls with viewModels attached to each. I want to click on a button within the first user control and get taken to the second user control.
I've looked at a few tutorials such as:
Example 1Example 2
Both those change screens from within the main window view model but I'd like to do it within the user control itself. Is it possible to either pass a command to change windows back to the main application view model, or have the user control model change the view on button click.
Edit: I figure I need to pass it as a command but I'm not sure how to pass which view I want along with it.

In MVVM, commands generally work at ViewModel level and have nothing to do with (or have any knowledge of) the View layer. To achieve what you have described, create a public property that controls the type of View you want to see. For example (using MVVM Light):
class YourViewModel : ViewModelBase
{
public string ActiveView
{
get { return _ApplicationMessage; }
//Note that this setter performs notification
set { Set(ref _ApplicationMessage, value); }
}
private RelayCommand<string> _SetViewCommand = null;
public RelayCommand<string> SetViewCommand
{
get
{
if (_SetViewCommand == null)
{
_SetViewCommand = new RelayCommand<string>((v) =>
{
ActiveView = v;
}
}
return _SetViewCommand;
}
}
}
As you see, your commands will simply manipulate the value of the public property to the appropriate value. Now your View can build on top of this property and use DataTriggers to load proper Content:
<Button Content="Normal" Command="{Binding SetViewCommand}" CommandParameter="Normal" />
<Button Content="Edit" Command="{Binding SetViewCommand}" CommandParameter="Edit" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ActiveView}" Value="Normal">
<Setter Property="Content">
<Setter.Value>
<UserControl1 />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ActiveView}" Value="Edit">
<Setter Property="Content">
<Setter.Value>
<UserControl2 />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Edit
Answering your comment:
view doesn't really interact with the viewModel: That's incorrect. View always interacts with the VM, it is the VM that doesn't know anything about the view. About your confusion, understand that you don't need to create one VM per user control. A VM is created against a View, not a control. So you'll probably have a single ViewModel named MainVM, exposing all the public properties that your view will bind to. Each User Control can then use only the properties that it is interested in. A trigger can then switch the view from one User Control to another when a particular property of the underlying VM is changed. The new User Control will then show correct data since it will be bound to the VM properties that it wants to work with.
Just remember that the VM is not interested in (nor have a way of) knowing how view is using it. It just needs to maintain its state correctly. View can then respond to the state changes and update itself accordingly.

Related

Xamarin MVVM: updating control styling (font size, border color etc.) by some calculation results done in ViewModel

I'm making first steps in learning Xamarin Forms and run into design question which can't find easy solution for. I want to utilize MVVM approach and implement it with best practices in mind, though I realize same thing could be done in different ways.
So I have Button and Label controls in View. Clicking button sends Command to ViewModel that runs some calculations (or delegates them to the model) and impacts text and visual representation of the Label in the text field. ViewModel has Text property to display in the textbox which could easily to be done with property bindings
<Label Text="Binding Text" ...></Label>
so once ViewModel modifies the text it's shown on a View updated, it's not a problem. The design in question is second part: I'm not sure how to update visual styling for the label, like border color and strikethrough style of font right after calculation is done in ViewModel, it's something that I believe only codebehind (xaml.cs) could properly do, not binding.
One of the approach would be to leave it as a command xaml -> VM -> M
<Button ButtonClickedCommand="{Binding RunCalculationsCommand}" ...>
and have VM to raise a certain ad-hoc event in ViewModel that codebehind catches and does it "styling" job: VM -> xaml.cs
Another is to have a regular event (not Command) raised on view that starts calculation in ViewModel and runs styling xaml -> xaml.cs -> VM -> M
<Button Clicked="RunCalculations_OnClicked" ...>
But then it's more tightly coupled and does not fully follows MVVM per my understanding.
Or I guess the best one could be something else that I'm not yet aware of
Two options: bind a trigger, or bind the control properties directly to VM.
Then, update VM properties in your button command.
Bind a trigger with preset styles.
<Label Text="{Binding Text}" TextColor="Green">
<Label.Triggers>
<DataTrigger
Binding="{Binding CalculationResultIsNegative}"
TargetType="Label"
Value="true">
<!-- set Specific Property -->
<Setter Property="TextColor" Value="Red" />
<!-- or set Specific Style in App.xaml -->
<Setter Property="Style" Value="{StaticResource RedStyleDefinedInAppXaml}" />
</DataTrigger>
</Label.Triggers>
</Label>
Update in your VM
void RunCalculationsCommandMethod ()
{
//if...
CalculationResultIsNegative = true;
//else..
//CalculationResultIsNegative = false;
}
Bind control properties in XAML
<Label Text="{Binding Text}"
TextColor="{Binding ColorPropertyFromVM"}
FontAttributes="{Binding FontAttributesProperty"} />
Update in your VM
void RunCalculationsCommandMethod ()
{
//if...
ColorPropertyFromVM = Color.Red;
FontAttributesProperty = FontAttributes.Bold;
}

Best practices for WPF - does every GUI element need a binding?

I am struggling to wrap my head around the real benefit of binding in WPF.
I have an application with a large textbox, designed for taking several hundred characters of user input. I have bound this to a "Text" string in my ViewModel. This works OK.
I also have a button with content "Submit". I need to change the content of this button once or twice, so I am doing it in the click event method in the window's code behind. I could, of course, bind the text to the ViewModel, but is it really worth it?
Should everything have a binding? What if I need to display a MessageBox? That will need some logic inside the onclick.
Should click events me as follows:
private void button_Login_Click(object sender, RoutedEventArgs e)
{
viewModel.DoSomething();
}
..where everything gets handed to the ViewModel?
I know this is a general question but I have tried my best to ask direct, answerable questions.
UI concerns are perfectly fine residing in your codebehind.
Business concerns should reside in your ViewModels. The commands and information they expose are what should be bound to elements in your UI.
Since changing the text in a button based on what the button is supposed to do is a UI concern, binding the text of the button to your ViewModel would be pointless.
I wouldn't put any code in codebehind. Create an ICommand property in your ViewModel and bind the buttons Command property to it. I use the ICommand implementation from MVVM Light (RelayCommand) but you can create your own or use one of the many other frameworks.
I'd then have a State property (ProcessStatus here) that I use a DataTrigger with to update the text on my button.
ViewModel
public ICommand LoginCommand
{
get
{
return new RelayCommand(() =>
{
ProcessStatus = Status.AUTHORIZING;
DoSomething();
});
}
}
private Status _processStatus;
public Status ProcessStatus
{
get { return _processStatus; }
set
{
if (value ==_processStatus)
return;
_processStatus= value;
RaisePropertyChanged("ProcessStatus");
}
}
View
<Button Command="{Binding LoginCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content"
Value="Submit" />
<Setter Property="IsEnabled"
Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ProcessStatus}"
Value="{x:Static enum:Status.AUTHORIZING}">
<Setter Property="Content"
Value="Authorizing..." />
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>

Pass info from Parent View to Child ViewModel

I have in my ParentView(DashboardConsultants) a gridview which shows a custom tooltip when the user's mousepointer is hovered over a cell. The tooltip show a View (AgreementDetails_View) which shows information of the Agreement binded to that cell. I will show the code I have now so you can better understand my question:
DataGrid Cell in ParentView:
<DataGridTextColumn Header="Okt" Width="*" x:Name="test">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Tag" Value="{Binding Months[9].AgreementID}"/>
<Setter Property="Background" Value="White" />
<Setter Property="DataGridCell.ToolTip" >
<Setter.Value>
<v:UC1001_AgreementDetails_View Background="#FFF" Opacity="0.88" />
</Setter.Value>
</Setter>
My ChildView:
public UC1001_DashBoardConsultants_View(UC1001_DashboardConsultantViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
In the ViewModel, I have following method to get the right Agreement from the database:
private void GetRefData()
{
UC1001_ActiveAgreementArguments args = new UC1001_ActiveAgreementArguments();
args.AgreementID = 3;
DefaultCacheProvider defaultCacheProvider = new DefaultCacheProvider();
if (!defaultCacheProvider.IsSet("AgrDet:" + args.AgreementID))
{
ConsultantServiceClient client = new ConsultantServiceClient();
AgreementDetailsContract = client.GetAgreementDetailsByAgreementID(args);
defaultCacheProvider.Set("AgrDet:" + args.AgreementID, AgreementDetailsContract, 5);
}
else
{
AgreementDetailsContract = (UC1001_ActiveAgreementContract)defaultCacheProvider.Get("AgrDet:" + args.AgreementID);
}
}
As you can see for now, the method always calls the same Agreement, (I did that for testing purposes) but now I want the Agreement which ID is specified in the DataGrid Cell Tag (in this example it's the Months[9].AgreementID).
I can give it to the ViewModel in my Child View's constructor, but I don't think that it's allowed due to the MVVM Pattern (or is it allowed?).
So my question is: How can I pass the AgreementID specified in my ParentView to the ChildView's ViewModel to get the right data for the ChildView?
Ofcourse, more information/code/clarification can be happily provided, just ask :)
Thanks in advance!
Not sure that I got question in righ way but my feelings like you need to use Commands instead of introducing tied coupling by passing back reference to parent itself
Personally, I feel that WPF Views should be nothing more than a pretty reflection of the ViewModel. So the View should not actually be passing any data to the ViewModels - instead it should be reflecting the ViewModel's data.
In your case, I would attach a property to the object that is displayed in each DataGrid Row. For example, if your DataGrid contained Agreement objects, I would ensure that each Agreement object had a property called AgreementDetails which can be viewed from the ToolTip
It's pefectly legal and valid to pass in that ID eitehr via a constructor or through a property. I'm not sure from your code, but if your parent is the one with access to your model, you can also pass teh model into your view model (i.e. via constructor, property, or method).
One thing I often do in this case is to add a property such as the following in the ViewModel of my Parent:
object ActiveItem {get;set;}
I then bind that ActiveItem to the ActiveItem in my grid.
<DataGrid SelectedItem="{Binding ActiveItem}">
</DataGrid>

How can I do that my userControl disappears when is validated?

I'm following some instructions from here:
WPF Window Return Value
I'm showing a user control in my window. The user control contains several controls, when is validated (I mean, the user has entered all the data) I need to show another user control.
I'm using a border like container.
Here's a way to do this. I don't know that it's the best way, but then I also don't know if this way of designing a UI is all that good in the first place. For instance, if my control vanishes when I put valid data into it, how do I get it back if I realize I made a mistake? There's a reason wizards have a "Back" button.
First, create a base view model class that exposes a boolean IsValid property. (In my example, I'm calling it ValidatingViewModelBase.) Each of your views will use a view model derived from this class. In each view model, once all of the properties are valid, set IsValid to true.
Next, create a user control for each view model as you'd do in any MVVM application. The UI will make more sense if they are designed to be the same size.
Now, create a view model that exposes instances of these view models as properties (in my example, they're called Page1, Page2, etc.) and create a view that's bound to it:
<Grid>
<Grid.Resources>
<Style x:Key="{x:Type ValidatingViewModelBase}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style>
<local:UserControl4 DataContext="{Binding Page4}"/>
<local:UserControl3 DataContext="{Binding Page3}"/>
<local:UserControl2 DataContext="{Binding Page2}"/>
<local:UserControl1 DataContext="{Binding Page1}"/>
</Grid>
(This assumes that the DataContext for the grid is already set.)
How this works: The Grid presents the controls in the same space on the screen, ordered from back to front. Since they're the same size, and they're opaque, only the last one presented is visible.
When the IsValid property gets set on the Page1 view, the style changes its Visibility to Hidden. Now page 2 is visible. This continues until all four pages are valid. Since we're setting Visibility to Hidden, and not Collapsed, the Grid remains the same size even after all four pages are invisible.
I haven't tested this, so I don't know how bad a time you're going to have with focus issues. When page 1 becomes invisible, you probably want the first control on page 2 to get the keyboard focus. You may need to look at the discussion of logical focus in the Focus Overview for ideas of how to deal with this.

WPF: How do I handle an event from a Model to dynamically update xaml in MVVM

I've hit a bit of a dead end in trying to figure this one out... Using the MVVM pattern in WPF, our C# Model fires an event to say something has happened. I want to be able handle that event in my ViewModel and then either kick of a storyboard or change the visibility of a hidden panel on the current Xaml Page. This has to be handled with no Code Behind.
I can sync for the event in my ViewModel, update a property to say what the name of that event is and fire a NotifyPropertyChanged even but how do I get that to either kick off a storyboard or map to a boolean true/false on the Visibility property of my Grid? The property I bind to hs to be the event name as different grids may be shown based on different events so I need a way of mapping this to a boolean. However the ideal solution would be to kick off a storyboard. I've looked at DataTriggers but they all seem to be linked to styles and not to actual pages.
Any ideas of how I can achieve this?
Thanks!
I've used this in the past to kick off a storyboard in code-behind
Storyboard animation = (Storyboard)this.FindResource("ShowPanelStoryboard");
animation.Begin();
This code goes behind the View, not in the ViewModel. Personally, I don't mind some code behind my View providing it is only related the View. In the project I used this in, I added a listener to the VisibilityChanged event and when it got changed to Visible, I ran the storyboard.
As for showing your popup, there's a few ways. One of my favorites was just adding an IsPopupShown property to the ViewModel, binding my panel's visibility to it, and setting it to true anytime the popup should be shown. The ViewModel then handles the events that trigger the popup being shown or not.
An alternative as suggested by Dave White is to use a converter. If your value is not always true/false then you could create a converter that checks if a bound value is equal to the ConverterParameter, and return a Visibility value.
From your comment, it seems to me like what you may want to do is expose an Event property of type object in your view model. When the view model receives an event, it sets Event to an object of a type appropriate for that event. In your XAML, you have this:
<ContentControl Content="{Binding Event}"/>
and in the resource dictionary define a DataTemplate for each specific type of event you want to display. If Event is null, nothing gets displayed. If Event contains an object that you've defined a DataTemplate for, it gets displayed using that template.
Yes, you'll need to create a class for each type of event (if you don't already have one).
Another way is to implement the poor man's template selector:
<TextBlock Text="This is displayed if Foo contains 'BAR'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAR">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="This is displayed if Foo contains 'BAZ'">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Property="Foo" Value="BAZ">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's kind of stupidly verbose, but it's an easy way to handling a lot of mutually-exclusive display options.
Bind the Visibility property on your grid, in Xaml, to the boolean property on your ViewModel.
<Grid Visibility="{Binding Path=VisiblePropertyOnViewModel}">
Now do whatever you need in your ViewModel and set the property. As long as it does INotifyPropertyChanged or is a DependencyProperty, it will work.
I'd have to do more digging to figure out how to kick off a Storyboard, but I have no doubt it would be almost as easy. Storyboards can be kicked off by PropertyTriggers as well I believe. I'll leave this to get you started.

Categories