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

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.

Related

WPF Treeview is not Collapsing Rows Even with Visibility Collapsed [duplicate]

I've been trying to just hide items from a TreeView. I'm using a custom data type as source (called SettingsMenuItem) which inherits from FrameworkElement (currently FrameworkContentElement, because otherwise the TreeView renders them wrong).
My goal is by setting the VisibilityProperty of these FrameworkElements to either Collapsed or Visible that I'm able to hide certain items (including their children). I know that this can be done by deleting items from the source collection. But that's not what I want. It would mean that I have to mirror each collection in order to keep track of it's actual items, bind to each one in order to be notified about Visibility-changes and create a new collection each time one changes. A lot of overhead for this.
Right now I have no clue how I could accomplish that. I figure it's related to the ItemsGenerator, but I haven't seen any possibility to override it's behaviour. I thought TreeView would be able to detect Visibility, but obviously it doesn't. As alternative I thought of a custom TreeViewItem (maybe even TreeView if necessary) - but at this point the abstraction of this whole system overwhelms me. I don't know where to start and what is actually necessary to solve the problem.
Tips what I have to change or implement by myself would be more than enough. A complete solution would be nice.
You can do this using a data trigger bound to a property (e.g. "IsVisible") in you tree data nodes:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
While this technically answers your question I would be wary of actually doing it. User3690202's comment is correct, it's the sort of thing you would normally do via filtering in your view model.
For alternate solution using code behind xaml.cs:
To Remove a specific TreeViewItem from a TreeView which is created from a code behind.
TreeViewItem treeViewItem1 = new TreeViewItem
{
Visibility = Visibility.Collapsed,
};
use the code with TreeViewItem you want to hide in a if condition to hide specific TreeViewItem Header let say "Cars" and you want to hide it and use the code with if condition to hide "Cars" TreeViewItem.

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

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.

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 to set focus to textbox using MVVM?

How to focus a textbox from ViewModel wpf?
<TextBox Name="PropertySearch"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay, Path=PropertySearch,
ValidatesOnDataErrors=True}"
Width="110"
Height="25"
Margin="10" />
You can do this by adding a property to your ViewModel (or use an existing property) that indicates when the SetFocus should happen but the View should be responsible for actually setting the focus since that is purely View related.
You can do this with a DataTrigger.
View:
<Grid Name="LayoutRoot" DataContext="{StaticResource MyViewModelInstance}">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding UserShouldEditValueNow}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=PropertySearch}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBox Name="PropertySearch" Text="{Binding UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Path=PropertySearch, ValidatesOnDataErrors=True}" Width="110" Height="25" Margin="10" />
</Grid>
ViewModel:
// When you think the view should set focus on a control
this.UserShouldEditValueNow = true;
The example above is simplified by just using a boolean ViewModel property "UserShouldEditValueNow". You can add a property like this to your ViewModel or use some other exising property that indicates this state.
Note: So why is it done this way in MVVM? One reason is, suppose the View author decided to replace the TextBox with a ComboBox, or even better, suppose your property was an integer value that had both a TextBox to view/edit the number and a Slider as another way to edit the same value, both controls bound to the same property... how would the ViewModel know which control to set focus on? (when it shouldn't even know what control, or controls, are bound to it in the first place) This way the View can select which control to focus by changing the ElementName binding target in the DataTrigger Setter.
Happy coding!
The question you should be asking yourself is "why does my ViewModel need to know which control has the focus?"
I'd argue for focus being a view-only property; it's an interaction property, and has nothing to do with the conceptual state. This is akin to the background color of a control: why would you represent it in the VM? If you need to manage the focus in a custom way, it's probably better to use a view-level object to do the job.
In your parent control, add the following property:
FocusManager.FocusedElement="{Binding ElementName=PropertySearch}"
While purists may argue for leaving this out of the VM, there are cases where it may make sense to do so from the VM.
My approach has been to make the view implement an interface, pass that interface to the ViewModel, and then let the VM call methods on the interface.
Example:
public interface IFocusContainer
{
void SetFocus(string target);
}
A couple things to keep in mind:
A VM might serve more than one instance of a view, so your VM might want to have a collection of references to IFocusContainer instances, not just one.
Code the VM defensively. You don't know whether there are 0, 1 or 20 views listening.
The "target" parameter of SetFocus() should probably be "loosely" coupled to the VM. You don't want the VM caring about the exact control names in the UI. Rather, the VM should indicate a name that is defined solely for focus management. In my case, I created some attached properties that would allow me to "tag" controls with "focus names".
To implement the interface, you can:
Implement it in the code-behind
Create some behaviors that know how to attach to the ViewModel that is present in the DataContext.
There's nothing wrong with implementing it on the Code Behind, but the behavior approach does allow a XAML only hookup if that's important to you.
In the implementation of the interface, you can use the visual tree to locate the control, or you could just code up a switch statement for a known set of focusable items.

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