This seems like such a basic question but after hours of searching around and not figuring out what I'm doing wrong I decided it's time to ask for help!
I'm new to WPF and the MVVM pattern, but am trying to create an application that has several windows you can navigate through by clicking buttons. This is accomplished by having the app window display UserControls using DataTemplates, so there's no content currently shared between pages (though there will be once I create the navigation area). Here's what the XAML looks like for the main window, with there currently only being one page in the application:
<Window x:Class="WPF_Application.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF_Application"
Title="ApplicationView" Height="300" Width="300" WindowStartupLocation="CenterScreen" WindowState="Maximized">
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginMenuViewModel}">
<local:LoginMenuView />
</DataTemplate>
</Window.Resources>
<DockPanel>
<ContentControl
Content="{Binding CurrentPageViewModel}" />
</DockPanel>
Now what I'd like to do is add a KeyBinding that reacts to the escape button being pressed. When this is done "LogoutCommand" should fire in the LoginMenuViewModel. I'm stuck getting the keybinding to trigger any commands within LoginMenuViewModel, and I've figured it's probably because the DataContext needs to be set to reference LoginMenuViewModel. For the life of me I can't get this to work.
Am I going about application-wide commands completely the wrong way? Is there some super simple fix that will make me smack my forehead in shame? Any insight is appreciated!
I do not know your ViewModel code, so it is not easy to give you details hints.
First of all, if your are using MVVM, you should have your own implementation of ICommand interface. You can find here the most common one (and a simple MVVM tutorial too).
After you have your own command class, your ViewModel should expose your LogoutCommand:
public class ViewModel
{
/* ... */
public ICommand LogoutCommand
{
get
{
return /* your command */
}
}
/* ... */
}
In your code behind you will set: DataContext = new ViewModel(); and at this point you can declare the KeyBindings that you need:
<Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding Path=LogoutCommand, Mode=OneWay}" />
</Window.InputBindings>
In this way when the Window is active and the user press the "Esc" key, your LogoutCommand is executed.
This is a brief summary that I hope will guide you in deepening the MVVM and its command system.
Related
I need to know when a parent in the VisualTree or LogicalTree changes of a Control. I need this feature, since anytime a parent changes I need to reevaluate the controls' window class, so that I can attached Command- and InputBindings.
I have a dirty way, which means I need to attach to each parent element and check for parent changes with events, but I was hoping there is another solution.
Example:
I have a UserControl
<UserControl x:Class="WpfApplication21.UserControl1">
<UserControl.CommandBindings>
<CommandBinding Command="ApplicationCommands.Cut" Executed="SomeHandler"></CommandBinding>
</UserControl.CommandBindings>
</UserControl>
and I have a Window that contains one or many UserControls
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication21"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.New" Executed="SomeHandler"></CommandBinding>
</Window.CommandBindings>
<StackPanel>
<local:UserControl1></local:UserControl1>
<local:UserControl1></local:UserControl1>
</StackPanel>
</Window>
Although in the example I have put the same command, it's only here to show in the example. In reality each control has different commands. The commands of each UserControl need to be merged into the CommandBindings of the Window (the same is true for InputBindings - not shown here). Each UserControl is a plugin that is created from a ViewModel dynamically, so each ViewModel has a different View (the example only shows UserControls, but in reality these a derivation from a UserControl).
For that reason I created a behavior that attaches to a CommandProvider that is implemented by the ViewModel (created by me) so that it's done without code-behind.
Since I have many ViewModels and therefore also Views I need to manage the Command-/InputBindings and attach them to the Window. One problem is that not every View can get a Focus, and somehow for UserControl event if they are focused the Command-/InputBindings don't work.
Is it a little clearer now? I now the situation is a bit complex.
I have a dirty way, which means I need to attach to each parent element and check for parent changes with events, but I was hoping there is another solution.
Sorry to disappoint you but there is no other way, i.e. there is no "AnyParentChanged" event or something that you can hook up to.
You need to somehow iterate through all parent elements and hook up an event handler to an appropriate event for each one of them. It's either this or reconsider your entire approach.
Depending on whether a parent in the visual tree "changes" may not be the best way to solve whatever you are trying to do.
Using Catel 3.9 and DevExpress 15.x
My Customer has asked that I make some UI changes to an app and I'm not sure it can easily be done.
Architecture:
There is a MainWindow with an associated View and ViewModel.
The MainWindow holds a TabControl where each Tab's content is a separate View/ViewModel. The MainWindow ViewModel does NOT own any of the nested VMs; they are constructed automatically by Catel at runtime by the View.
The old UI had buttons on each TabItem which allowed the Customer to load, save, display, filter, etc. The commands/properties for these buttons were bound directly to the ViewModel for that tab and was working fine.
The Customer would rather have a single top level (on the MainWindow) menu and selections from that menu would affect whichever tab had the current focus.
I can pass commands (using Messaging or Catel's InterestedIn attribute) to the correct ViewModel, but I'd like to have a more direct binding with the top level menu and the appropriate ViewModel so I can enable/disable menu items or even modify the text to suit whichever tab is open.
I'm looking for a primarily XAML and/or Catel solution. If you need additional information, please let me know.
Suggestions?
thanks,
randy
Edit: Sorry that I didn't include additional research. If you knew me, you'd know I will spend hours/days looking for solutions to problems and only when I'm stumped will I ask for assistance. My bad for not including more.
The hardest part about this issue is defining good search parameters. Most of the suggestions were similar to: Just put everything into the MainWindow ViewModel which (to me) is not a good design choice because what is displayed on the tabs IS different and should be separate.
Other solutions were to have the MainWindow ViewModel construct each of the inner ViewModels and then manage them. With the Catel framework I'm using, the framework automatically constructs the VM when the View is loaded, injecting any required parameters to the constructor. See below -- you just reference the View and Catel will match it up with its ViewModel and create it for you. Unfortunately without taking other steps, you really don't have a reference to the VM that was created.
MainWindow.xaml:
<dx:DXTabControl x:Name="MainTabControl"
Grid.Row="1"
Margin="10"
BorderThickness="0"
SelectedIndex="{Binding SelectedTabIndex}"
>
<dx:DXTabItem Header="Getting Started" IsEnabled="True">
<views:GetStarted />
</dx:DXTabItem>
<dx:DXTabItem Header="Validate Student Records" Background="Ivory">
<views:StudentValidation />
</dx:DXTabItem>
<dx:DXTabItem Header="Validate Teacher Records" Background="Ivory">
<views:TeacherValidation />
</dx:DXTabItem>
<dx:DXTabItem Header="Validate Admin Records" Background="Ivory">
<views:AdminRecordValidation />
</dx:DXTabItem>
</dx:DXTabControl>
Examples of some possible solutions I'm looking at:
One ViewModel Member of Another
Get a property of a tabitem's ViewModel when it's selected
Edit #2: There was a suggestion about using a Service and SO doesn't allow you to add detailed comments (and it puts a TIMER on your comments -- SHEEESH!), so I'll put my response here.
Consider this scenario using a Service: The Customer starts the app and clicks on a Tab (StudentValidation). The MainWindowViewModel (via a Property) detects the selected tab and calls the Service with an update (I'm not sure what is updated; possibly some sort of State). The "nested" ViewModels are notified (via an Event) of the change in the Service. I'll assume the StudentValidationViewModel is the only one who actually responds to the event and interacts with the Service, retrieving "data".
So, now we have the StudentValidation tab displayed and the Customer goes to the Main Menu of the app. The Main Menu is STILL tied to the MainWindow and every command is bound to the MainWindowViewModel. How does the Service bind the Main Menu to the ViewModel of the currently selected tab so that the commands will be handled by the StudentValidationViewModel? I'm probably missing something.
Use a singleton model which holds the shared data so you get the instance from wherever you like.
Services are the solution. Create a solution that is injected into all view models. Then the top-level vm can update the service, and all vm's can respond to the update via events.
Remember that vm's just represent a live view in memory so you can interact with them.
Thank you for all the suggestions. I tried to upvote each one, but as a "newbie" I can't. As I worked thru each one, I realized that all I wanted to do was bind a specific subset of the Main Menu items to the View/ViewModel of the currently focused tab on the Main Window. It seemed as simple as changing the DataContext of the menu item.
Here's the Main Menu. The FileSubMenu is the one I need to bind to the currently focused ViewModel. The other menu items can be handled by the MainWindowViewModel.
MainWindow.xaml:
<dxb:MainMenuControl Grid.Row="0"
HorizontalAlignment="Left"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Top"
BarItemDisplayMode="ContentAndGlyph"
>
<dxb:BarStaticItem Content="Validator" Glyph="pack://application:,,,/LexValidator;component/Images/lex-logo.png" />
<dxb:BarSubItem x:Name="FileSubMenu" Content="File">
<dxb:BarButtonItem Content="{Binding LoadRecordsText}" Glyph="{dx:DXImage Image=LoadFrom_16x16.png}" Command="{Binding LoadRecordsFile}"/>
<dxb:BarButtonItem Content="Clear Display" Glyph="{dx:DXImageOffice2013 Image=Clear_16x16.png}" Command="{Binding ClearDisplay}"/>
<dxb:BarButtonItem Content="FTE Counts..." Glyph="{dx:DXImage Image=TextBox_16x16.png}" Command="{Binding ShowFTECounts}"/>
<dxb:BarButtonItem Content="Show Pay Grid..." Glyph="{dx:DXImage Image=Financial_16x16.png}" Command="{Binding ShowPayGrid}"/>
<dxb:BarItemLinkSeparator />
<dxb:BarCheckItem Content="Show Ignored Issues" Glyph="{dx:DXImage Image=ClearFilter_16x16.png}" IsChecked="{Binding ShowIgnoredIssues}" IsEnabled="{Binding IsShowIgnoredIssuesEnabled}" />
</dxb:BarSubItem>
<dxb:BarSubItem Content="Exit">
<dxb:BarButtonItem Content="Exit" Glyph="{dx:DXImage Image=Close_16x16.png}" Command="{Binding ExitApplication}"/>
</dxb:BarSubItem>
<dxb:BarSubItem Content="Help">
<dxb:BarButtonItem Content="About..." Glyph="{dx:DXImageGrayscale Image=Index_16x16.png}" Command="{Binding ShowAboutBox}"/>
</dxb:BarSubItem>
</dxb:MainMenuControl>
Then on the TabControl, I handle the event when a new tab is selected:
MainWindow.xaml.cs
private void MainTabControl_SelectionChanged(object sender, TabControlSelectionChangedEventArgs e)
{
// Turn it off and see if it needs to be enabled.
this.FileSubMenu.IsEnabled = false;
var newTabItem = e.NewSelectedItem as DXTabItem;
if (newTabItem != null)
{
var tabView = newTabItem.Content as UserControl;
if (tabView != null)
{
var tabViewModel = tabView.ViewModel;
if (tabViewModel != null)
{
this.FileSubMenu.DataContext = tabViewModel;
this.FileSubMenu.IsEnabled = true;
}
}
}
}
I realize this may not be very "MVVM", but it works well and the "Boss" said "Move on to something else". I would be happier if the above code could be handled totally in XAML -- some sort of resource maybe?
Again, if there is something I missed or a better (more MVVM) solution, please let me know.
I know there are a lot of questions about WPF navigation, for application developed with MVVM pattern, and I have read tens and tens of answers but I'm missing probably something.
I started building an application following Rachel's article here. All works just fine, there's an ApplicationView Window with this XAML:
<Window x:Class="CashFlow.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:CashFlow.ViewModels"
xmlns:v="clr-namespace:CashFlow.Views"
Title="ApplicationView" Height="350" Width="600" WindowStartupLocation="CenterScreen">
<Window.Resources>
<!--Here the associations between ViewModels and Views-->
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<v:HomeView />
</DataTemplate>
</Window.Resources>
<!--Define here the application UI structure-->
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"
Margin="2,5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
The ApplicationViewModel, that is set as DataContext for this window when the application starts, maintains an ObservableCollection of my ViewModels. Thanks to data templates, it's possible to associate every view with its viewmodel, using a ContentControl to render the views. Navigation in this case is accomplished with a "side bar" of buttons, binded to ApplicationViewModel commands that perform the changes of CurrentPageViewModel object.
I'm wondering how I can perform navigation without the presence of that sidebar of Buttons. Having only the Content control, I should be able to change the CurrentPageViewModel from the others viewmodel? Probably the answer will be very trivial, but I can't see that right now.
Your top level homeviewmodel can orchestrate navigation via an eventbus pattern. To use eventbus, you would inject an object that tracks objects that want to be notified of events. Then when a view model raises an event, the homeviewmodel receives it and performs the currentpageviewmodel assignment that will navigate you to the next viewmodel.
Ex:
Messenger defines two methods - RegisterForEvent<IEvent>(ViewModel aViewModel), and RaiseEvent(IEvent event).
So you would define a function to subscribe to the events -
HomeViewModel.cs
...
void SubscribeForEvents() {
Messenger.RegisterForEvent<NavigationEvent>(this);
}
Then you inject the Messenger into your other view models, and from those view models, raise the event:
Messenger.RaiseEvent(new NavigationEvent { TargetViewModel = new TargetViewModel() });
Where the event is something like
public class NavigationEvent : IEvent {
ViewModel TargetViewModel { get;set;}
}
C Bauer is right with what you are missing. I found in order to switch the data context, you'll need a messenger service to flag your "applicationviewmodel" to switch its data context. A good discussion with the steps you need are spelled out in a discussion here.
Register the message to be received in your applicationviewmodel, then handle the data context switch in your receive message function.
Also, this might be true or not, but I had to use 1 window, with multiple user controls as opposed to multiple windows if I wanted to have 1 window showing at all times. Lastly, I followed Sheridan's example and defined my data templates in my app.xaml as opposed to the window itself.
I am creating a page-based WPF application using MVVM. I have created a custom (non dependency object) helper class to centralize navigation. This class is created as a resource of my main window like so.
<Window.Resources>
<local:NavigationManager x:Key="NavigationManagerKey" x:Name="NavigationManager"/>
</Window.Resources>
The class contains an ICommand that I have exposed publicly so that it can be used in XAML. However, I am struggling to find out how to bind to it. I would prefer not to have to set it as the data context for the page as that is already in use. Normally, I bind to a command like so (when I am binding to a command on the data context)
<Button Header="Image" Command="{Binding CreateImageAssetCommand}"></Button>
Thanks for any help with the matter.
You can set the source of the binding:
<Button Header="Image" Command="{Binding CreateImageAssetCommand, Source={StaticResource NavigationManagerKey}}"></Button>
What would be the best way to build a data-navigation like in access-forms in XAML/C#?
Should I build a user control (or even custom control) that I just bind to my collection in which I can put other controls? (hence this question: C# User Control that can contain other Controls (when using it) )
Or can I build something by deriving from then ItemsControl somehow? how?
Or would this be done completely different today (like "this style of navigation is so last year!")?
I'm relatively new to C# and all (not programming as such, but with more like "housewife-language" Access-VBA) also I'm no native english speaker. So pls be gentle =)
You can create user control and place a bunch of buttons (First, Prev, Next, Last, etc..) in it and place it on the main window. Secondly, you can bind your data navigation user control to a CollectionViewSource which will help you to navigate among your data.
Your main window:
<Window.Resources>
<CollectionViewSource x:Key="items" Source="{Binding}" />
</Window.Resources>
<Grid>
<WpfApplication1:DataNavigation DataContext="{Binding Source={StaticResource items}}" />
<StackPanel>
<TextBox Text="{Binding Source={StaticResource items},Path=Name}" />
</StackPanel>
</Grid>
Your Data Navigation User Control:
<StackPanel>
<Button x:Name="Prev" Click="Prev_Click"><</Button>
<Button x:Name="Next" Click="Next_Click">></Button>
<!-- and so on -->
</StackPanel>
And your click handlers goes like this:
private void Prev_Click(object sender, RoutedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(DataContext);
if (view != null)
{
view.MoveCurrentToPrevious();
}
}
I hope this helps.
Sounds like you're after a DataGrid control. Microsoft is releasing a WPF DataGrid as part of a WPF Toolkit which you can download here: http://wpf.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=25047.