How is it possible to re-use and compose parts in CM managed windows? I have found posts regarding using two UserControls to bind to the same ViewModel, but not so much if I want to have multiple views and viewmodels all composed in the same window. (a viewmodel for each view composed into a "master view")
The first part of my question would be how to break up components for re-use? If I have two areas of a window where one is a datagrid and another is a details view with labels and text boxes should these be in separate usercontrols, customcontrols or windows? Each one would ideally be stand alone so they can be separated and used in other windows.
So I would end up with 2 viewmodels and 2 views if they were separated. Now lets say I would like to create 3 windows, one window with the first view, the second with the second view and a third with both views. How do I use CM to create the window for each and wire up each view to their viewmodel? From the examples I have seen I see for the most part a single view and viewmodel in a window.
I'm not going to claim to be an expert in CM by any means, but I've had reasonable success with a simple "benchmark explorer" I've been writing. That uses a single "shell view" that composes two other views, each with its own ViewModel. The shell view looks like this:
<Window x:Class="NodaTime.Benchmarks.Explorer.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NodaTime Benchmarks" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
<ContentControl x:Name="BenchmarkPicker" Grid.Column="0"/>
<GridSplitter ... />
<ContentControl x:Name="ResultsGraph" Grid.Column="2"/>
</Grid>
</Window>
then ResultsGraphView and BenchmarkPickerView are each like this:
<UserControl x:Class="NodaTime.Benchmarks.Explorer.Views.ResultsGraphView"
... namespaces etc ...>
<Grid>
<Grid.RowDefinitions>...</Grid.RowDefinitions>
<Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
... controls ...
</Grid>
</UserControl>
The ShellViewModel exposes the other two ViewModels as properties. Those are then passed to the views automatically on construction. (The bootstrapper doesn't provide any way of getting them.)
Now this doesn't quite fit your description, because I don't think you could use the two individual views individually as windows - I suspect you would end up with 5 views in total:
SubViewOne - a UserControl with the first view parts
SubViewTwo - a UserControl with the second view parts
JustViewOne - a Window containing just SubViewOne
JustViewTwo - a Window containing just SubViewTwo
BothViews - a Window containing both SubViewOne and SubViewTwo
I don't think there's a way of getting around the fact that you don't want one Window within another, and the top level window has to be... well, a Window.
Hope this helps, and let me know if you want more details of the small project where I'm doing this - it's far from production quality, particularly in terms of DI, but it may be enough to help you get going.
I think I've previously done something similar to what you're asking. I'd been playing around with one of the TabControl with the intention of hosting several different tools for a game I enjoy playing.
The main tool is an item browser similar to the usual file explorer type programs, and similar to what Jon has described above. I'll explain some of the parts which may be of interest/relevance (I've removed some of the slightly obscure naming).
The main ExplorerView tab is essentially exactly the same the one Jon describes (which is hopefully a good sign - means I'm not crazy =D)
<UserControl x:Class="ItemsBrowser.Views.ItemsTabView"
<!-- namespaces -->
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<ContentControl x:Name="ItemsExplorer" Grid.Column="0" Grid.Row="0" />
<GridSplitter HorizontalAlignment="Right" VerticalAlignment="Stretch"
ResizeBehavior="PreviousAndNext" Width="4" Grid.Column="1" Background="#FFAAAAAA" />
<ContentControl x:Name="PanelView" Grid.Column="2" Grid.Row="0" />
</Grid>
</UserControl>
The associated ViewModel holds two other ViewModels, used for composing the main explorer view:
public class ItemsTabViewModel : Conductor<IScreen>.Collection.AllActive
{
public ItemsViewModel ItemsExplorer { get; set; }
public ExplorerPanelViewModel PanelView { get; set; }
// Ctor etc.
}
The ItemsExplorer hosts a TreeView style control, allowing users to explore various categories of Item from the game. This is used in multiple places in the application, and is composed into a few different controls.
The ExplorerPanelView is a panel on the right hand side, that changes to display a number of ViewModels, based on what type of item the user is viewing. The user also have the option to toggle a few different Views over the ViewModel displayed in the ExplorerPanelView.
The ExplorerPanelView looks like:
<UserControl x:Class="MIS_PTBrochure.Views.ExplorerPanelView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cal="http://www.caliburnproject.org">
<Grid>
<ContentControl cal:View.Model="{Binding Path=ActiveItem}"
cal:View.Context="{Binding Path=ActiveItem.State}"
Content="Select a folder."/>
</Grid>
</UserControl>
And the ExplorerPanelViewModel behind:
public class ExplorerPanelViewModel : Conductor<IScreen>.Collection.OneActive,
IHandle<ItemSelectedEvent> // More events.
{
public ItemViewModel ItemInfo { get; set; }
public CategoryFolderViewModel CategoryFolderInfo { get; set; }
public ExplorerPanelViewModel()
{
// My helper to access the `Caliburn.Micro` EventAggregator.
EventAggregatorFactory.EventAggregator.Subscribe(this);
// Other code
}
public void Handle(ItemSelectedEvent message)
{
// Other code to check active status
ItemInfo = message.selected;
ActivateItem(ItemInfo);
}
protected override void OnDeactivate(bool close)
{
Debug.WriteLine("Deactivated " + this.ToString() + close.ToString());
if (close) { EventAggregatorFactory.EventAggregator.Unsubscribe(this); }
base.OnDeactivate(close);
}
// Other code
}
I've tried to remove a lot of non-relevant code. Essentially I'm again hosting multiple ViewModels as properties (although you could hold a collection) and activating the relevant ViewModel when an approriate event is raised by my ItemsExplorerViewModel. I'm using the Caliburn.Micro EventAggregator to handle communication between multiple ViewModels.
In theory you could dispense with the properties, and just activate the ViewModels referenced in the events themselves.
Regarding the cal:View.Context and cal:View.Model - I'm using these all the user to toggle different available View states available (each ViewModel displayed in that panel inherits from a base ViewModel class which all have a State property).
There are a few places where I pop up different windows using some of the same Views and ViewModels. To achieve this, I make use of the Caliburn.Micro WindowManager. There isn't a great deal about it in the official documentation (you're best off searching Google and the CM discussions), it pretty does what is says on the tin.
If you have a look at the Caliburn.Micro.IWindowManager interface you'll see some handy methods that you can call from a WindowManager instance.
public interface IWindowManager
{
bool? ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null);
void ShowPopup(object rootModel, object context = null, IDictionary<string, object> settings = null);
void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null);
}
So to pop up a new Window with a ViewModel of your choice, I did something along these lines:
// Some basic Window settings.
dynamic settings = new ExpandoObject();
settings.Title = "Test Window";
settings.WindowStartupLocation = WindowStartupLocation.Manual;
settings.SizeToContent = SizeToContent.Manual;
settings.Width = 450;
settings.Height = 300;
var TestViewModel new TestViewModel();
WindowManagerFactory.WindowManager.ShowWindow(this.classSearch, null, settings);
Caliburn.Micro should again, resolve your Views to the correct ViewModels.
Hopefully there's something useful in there somewhere. I sort of arrived at this solution through a few design iterations, so this may not be the optimal approach to some of these problems. If anyone has any constructive criticism, please let me know =D
Related
I stumbled around an issue in which I did not find a solution in the official documentation of CM.
Our Application looks similar to this image:
While searching StackOverflow, I found solutions in doing something like this with the Conductor<object>.Collection.AllActive class (more active screens / views in shell caliburn micro or another MVVM framework). The biggest problem I ran into is, that I can't find a way to make the dependency injection, using a factory. The constructor of the ShellViewModel currently looks like this.
public ShellViewModel(
Func<FooterViewModel> footerViewModelFactory,
Func<LoginViewModel> loginViewModelFactory)
{
this.ActivateItemAsync(loginViewModelFactory);
this.ActivateItemAsync(footerViewModelFactory);
}
Obviously this does not work, because the ContentControl can not display the factory and needs a Screen. But how do I manage to bind the objects to the shell in the first place, while still maintaining the features of dependency injection? Otherwise an easy workaround would just be to create a new instance for the ViewModels and pass down all of the parameters, which is just super dirty in my eyes.
I finally figured it out. While I hate the factory pattern in Java, it seems necessary in Caliburn Micro and makes sense.
Anyways, I created a factory similar to this:
public class FooterFactory
{
// Add all the needed interfaces you get injected from the IoC container here.
private readonly IMyInterface myImplementation;
public FooterFactory(IMyInterface myImplementation, ...)
{
this.myImplementation = myImplementation;
}
//Creates the ViewModel. You can also pass some parameters here.
public FooterViewModel Create(...)
{
return new FooterViewModel (..., this.myImplementation);
}
}
After the factory is then added to the bootstrapper it can then easily be added like followed. I had some issues with my initial approach though. Therefore, I changed my code a little bit.
public ShellViewModel(
FooterFactory footerFactory,
Func<LoginViewModel> loginViewModelFactory)
{
this.ActivateItemAsync(loginViewModelFactory);
this.FooterViewModel = footerFactory.Create(...);
}
And my XAML looks something like this.
<Window x:Class="My.Client.ShellView"
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"
WindowState="Maximized"
WindowStartupLocation="CenterScreen"
Title="Dummy" Icon="/Resources/favicon.ico">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="120" />
</Grid.RowDefinitions>
<ContentControl Name="ActiveItem" Grid.Row="0"/>
<ContentControl Name="FooterViewModel" Grid.Row="1" />
</Grid>
</Window>
Note: As I said, this is a simplified version of the code I use. Anyway, it should highlight the approach on how to solve this issue. The good thing about this approach is, that you do not need the Conductor<>.Collection with multiple item controls. Therefore, you do not need to mess around with the order of the objects, if for example one object takes longer to load than the other, or you have to deactivate something. + you can create multiple ContentControls in the XAML if you want more stuff on your screen (e.g. Header, Menubar, etc.).
Preface: It's a bit hard to explain my problem in the form of a question so let my explain below.
Context: I'm developing a UI for an audio mixer (see picture) and as part of that I have a row of 16 "Channel Strips" (a UserControl) each with a fader. In the model though, the mixer has 32 channels (+ Auxiliaries). To remove the need for a super large ItemsControl with a scroll bar then I want to implement a page system to switch which channels in the model the UI is bound too.
Mock up of the UI.
After some reading of examples on MVVM architecture I narrowed it down to too ways to implement this but I have problems with each.
Bind the Channel Strips in the UI directly to an ObservableCollection in the ViewModel and then use property notifiers to bind that to the data in the model:
<ItemsControl x:Name="FaderPane1_8" Background="{DynamicResource FaderPanel}" Margin="0" ItemsSource="{Binding Faders}" ScrollViewer.CanContentScroll="False" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ChannelStrip MaxWidth="50" FaderValue="0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
MainWindow.xaml
public ObservableCollection<ChannelStrip> Faders
{
get { return _faders; }
set { SetProperty(ref _faders, value); }
}
public void Init()
{
//Create a new mixer
VMixer = new Mixer(32, 8, 8);
VMixer.MixChannels.CollectionChanged += MixChannels_CollectionChanged;
}
//More code to handle "MixChannels_CollectionChanged" as well as changes from ViewModel to Model
ViewModel.cs
The problem is that this creates lots of event handlers for property changes in every direction as well as ends up with a copy of the faders in memory. All of this seems like a poor design to me but is what I have seen dictated by convention because it allows for complete separation of Model from ViewModel and View as well as no direct link from View to Model.
Bind directly to a part of the model and use the ViewModel to rebind the UI channel strips to a different set of channels every time the page is changed.
This seems more sensible in that I'm not created unnecessary copies of data and events, and might result in less messy code.
<Grid x:Name="FaderPane9_16" Background="{DynamicResource FaderPanel}" Margin="0" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<local:ChannelStrip/>
<local:ChannelStrip Grid.Column="1"/>
<local:ChannelStrip Grid.Column="2"/>
<local:ChannelStrip Grid.Column="3"/>
<local:ChannelStrip Grid.Column="4"/>
<local:ChannelStrip Grid.Column="5"/>
<local:ChannelStrip Grid.Column="6"/>
<local:ChannelStrip Grid.Column="7"/>
</Grid>
MainWindow.xaml
public void UpdateFaderBindings(ChannelType faderMode, int faderModePage)
{
//[Code omitted for simplicity]
for (int i = 0; i < channels; i++)
{
int newFaderIndex = -1;
//[Code omitted for simplicity]
Binding b = new Binding("VMixer.Channels[newFaderIndex].FaderValue");//I know this is wrong I'm trying to demonstrate the idea
ChannelStrips[i].SetBinding(ChannelStrip.FaderValue, b);
}
}
ViewModel.cs
Both of these architectures clearly have their own disadvantages and after hours of reading I'm getting nowhere in deciding what the correct architecture in this situation is.
I write WPF airport check-in systems for a living, and I encounter this problem all the time in the form of aircraft seat selection. There isn't enough space to display all 525 seats of an A380, so I have to display sections of the fuselage and allow passengers to scroll through them. Similarly, a check-in kiosk may need to present 50 different airlines for a passenger to select from, so again paging is used to scroll through them.
What you're really try to do here is implement a form of virtualization. That, in turn, requires view logic. And whenever you have view logic that doesn't require direct interaction with an actual GUI element the correct place to put it is almost always in the view model layer. In a commercial application this is absolutely behavior you would want to unit test, but with option #2 you can't do that without the GUI elements actually being present.
The reason option #1 looks messy is because it is, despite being closer to pure MVVM. What you really should be doing is creating a 1:1 relation between your views and your view models. I personally would create a MixerViewModel class for each of your mixers (visible or no) containing information needed by just that mixer, and also maintain a list for the ones that are currently visible:
private IList<MixerViewModel> AllMixers;
public ObserveableCollection<MixerViewModel> VisibleMixers {get; set;} // would probably also need INPC
The first list is for all 32 of your mixers and gets created at start-up. That second list is what's currently visible, you populate it with elements from the first list whenever the current page changes. Doing it this way facilitates complete separation of concerns, it makes it very easy to change the total number of items (either in total, or visible at once) and you also now have the ability to unit-test. Yes, it means GUI items are being created and destroyed whenever the page changes, but that's how WPF has been designed to be used and as long as you don't go overboard your application will remain responsive.
Definitely the first solution is more suitable but I would perhaps in addition try to avoid all handlers by simply creating 2 separate ItemsControls in UI (bound to the same ObservableCollection) on top of each other with second hidden and with buttons that change channels simply revert the visibility of both ItemsControls.
I have 2 combobox, which include menus name and its details like (Pie-apple, chocolate), (Juice- apple, orange).
So if I select 'Pie' in the first combobox, second one should have its details-apple and chocolate.
Xaml code is below:
<DockPanel Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" VerticalAlignment="Center" Margin="5">
<TextBlock Text="Menu : " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstMenu}" SelectedValue="{Binding SelectedMenu}" HorizontalAlignment="Left" Height="32"/>
<TextBlock Text="Detail: " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstDetail}" SelectedValue="{Binding SelectedDetail}" HorizontalAlignment="Left" Height="32" SelectionChanged="combobox_changed"/>
</DockPanel>
and viewmodel is below:
private List<string> lstMenu;
public List<string> LstMenu {
get { return lstMenu; }
set {
lstMenu = value;
RaisePropertyChanged("LstMenu");
}
private string selectedMenu;
public string SelectedMenu {
get { return selectedMenu; }
set {
selectedMenu= value;
RaisePropertyChanged("SelectedMenu");
LstDetails = new ObservableCollection<string>( //get its details list from DB );
}
private ObservableCollection<string> lstDetail;
public ObservableCollection<string> LstDetail {
get { return lstDetail; }
set {
lstDetail= value;
RaisePropertyChanged("LstDetail");
if (lstDetail != null && lstDetail.Count > 0) {
SelectedDetail = lstDetail.FirstOrDefault();
}
}
private string selectedDetail;
public string SelectedDetail {
get { return selectedDetail; }
set {
selectedDetail= value;
RaisePropertyChanged("SelectedDetail");
}
The problem is, if I selected Pie-apple and then changed the first combobox to Juice menu, Juice-apple is automatically selected but RaisePropertyChanged("SelectedDetail"); does not work.
I know that's because the selected two strings apple are same...
However I need to call the event SelectionChanged="combobox_changed" since it contains UI reload event.
Moreover, if I added IsAsync=True into the LstDetail combobox, it definitely works but SelectionChanged="combobox_changed" is called twice. I don't know why.
what should I do?
It's hard to know exactly what the architecture of your program is, without a good Minimal, Reproducible Example. But, from what you've posted, it seems you are trying to do too much of the work, and not letting WPF do its fair share. In particular, you seem to have just one "view model" type, and in that object you attempt to completely define everything that the user sees, and the state of that.
As has been noted in the comments, this has led to a situation where there are pieces of state that relate to each other, but where you haven't done enough work to make sure they stay synchronized with each other. You've delegated some of the work to WPF, but it doesn't have enough context to let you know when something important has changed, and so your UI winds up in a inconsistent state.
It would in fact be possible to fix your program as it stands now, by forcing the SelectedDetail property to refresh itself if the menu combo box changes. But a) I can't tell you exactly how to do that, because you've omitted all of the details that relate to the actual management of that property (such as the combobox_changed() event handler), and b) that's really just too much work anyway.
The first thing you need to get on board with, when writing WPF code, is to put as much of your program specification into declarations, and not procedures. WPF's binding mechanisms do a great job of automatically keeping view model data structures in sync with the actual UI. This means that you can view your program as two completely independent entities: the user interface itself, and the "business logic", i.e. the things your program actually has to do. The "view model" part mediates between these two elements. In the simplest WPF programs, the business logic itself can be entirely encapsulated in the view model data structures; in more complex applications, the view models focus on mapping between the UI ("view") and the business logic ("model").
This has an important implication: if you find yourself writing code that is directly interacting with the view element of your program — i.e. either responding to the UI or modifying it — that code had better be strictly specific to the view. Another way to look at that is, such code should be reusable with any other type of business logic, just as all of the built-in XAML stuff is completely reusable and not at all specific to your business logic.
Conversely, if that code you're writing is fiddling with the view model data structures directly or, even worse, is actually part of the view model data structures, you've gone off into the weeds. This should never happen.
You can use these two metrics to constantly evaluate as you go along whether you're designing the code correctly, and to help guide that design before you actually write the code.
Okay, with that little bit of indoctrination out of the way, here's how I would implement your stated goal:
You need some view models. Not just one, because you have a hierarchy of business logic objects, and the view models need to reflect that. Working from the bottom up:
You need a view model that can represent the detail to be displayed. For example:
class DetailViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private string _description;
public string Description
{
get => _description;
set => _UpdateField(ref _description, value);
}
private decimal _price;
public decimal Price
{
get => _price;
set => _UpdateField(ref _price, value);
}
}
Notes:
The above relies on a base class NotifyPropertyChangedBase I use for all view models, which provides a convenient mechanism to implement observable properties. Code for that is provided below.
The above is strictly a simple data container. For this example, that's all that's needed, because all the example is concerning itself with is how to react to UI input, and WPF is great at managing that already, as long as it has a place to keep everything. A more interesting WPF program would have procedural aspects in the view model for providing commands that operate on the data beyond what the XAML is capable of defining.
Okay, so with a details data structure, we also need a place to keep a list of these objects for each type of menu in your program:
class MenuViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private List<DetailViewModel> _menuItems = new List<DetailViewModel>();
public List<DetailViewModel> MenuItems
{
get => _menuItems;
set => _UpdateField(ref _menuItems, value);
}
private DetailViewModel _selectedItem;
public DetailViewModel SelectedItem
{
get => _selectedItem;
set => _UpdateField(ref _selectedItem, value);
}
}
You'll note that the above two view model data structures have a Name property. This is used to display to the user the name of the item they will be selecting.
The new aspect in this view model is the list of menu item objects, and then a property that keeps track of the currently selected menu item object. This is critical with respect to your question: in your implementation, the only thing you know about the currently selected item is its name. But when the same name appears on two different menus, you've got no way to distinguish the two. The only way out of that dilemma, given the design you chose, is to always refresh the details explicitly when the selected menu changes.
But here, we tie the selected item to the menu itself. This gives us two nice results:
When the menu changes, then whatever's bound to the selected item property will change as well, implicitly updating the displayed details, because WPF's binding engine understands the relationships of the properties involved. In particular, the details aren't just some random string, but rather a specific object that was retrieved from a different specific object. If that latter specific object is no longer the context for the binding (i.e. the user picks a new menu), then WPF knows that the former specific object needs to be re-evaluated.
By default, the user's selection for a given menu is remembered, because each menu has its own SelectedItem property! When the user selects an item from a menu, then selects a different menu, then after they are done with that second menu and go back to the first, the first will still have their previous selection from that menu. Now, this may or may not be the desired behavior. If not, it's reasonably easy in the view model to reset the selected item when the menu changes. But it's usually easier to suppress functionality than to create it, so having the default behavior provide that added functionality is nice.
Finally, of course, we need a place to keep track of the currently selected menu:
class MainViewModel : NotifyPropertyChangedBase
{
private List<MenuViewModel> _menus = new List<MenuViewModel>();
public List<MenuViewModel> Menus
{
get => _menus;
set => _UpdateField(ref _menus, value);
}
private MenuViewModel _selectedMenu;
public MenuViewModel SelectedMenu
{
get => _selectedMenu;
set => _UpdateField(ref _selectedMenu, value);
}
}
Just like with the menu object, this one has both a list of items (menus, in this case) and a property that keeps track of which specific item is selected.
Now that the view model data structures have been correctly designed to reflect the hierarchy of user selection in our user interface, it's a very simple matter to declare the user interface to work with those data structures:
<Window x:Class="TestSO58167153WpfTwoLevelDetail.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:l="clr-namespace:TestSO58167153WpfTwoLevelDetail"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="comboBoxNameTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:DetailViewModel}">
<StackPanel>
<TextBlock Text="{Binding Description}"/>
<TextBlock Text="{Binding Price, StringFormat={}Price: ${0:0.00}}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" Width="100"
ItemsSource="{Binding Menus}" SelectedItem="{Binding SelectedMenu}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ComboBox Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Left" Margin="10,0"
ItemsSource="{Binding SelectedMenu.MenuItems}" SelectedItem="{Binding SelectedMenu.SelectedItem}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ContentPresenter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Content="{Binding SelectedMenu.SelectedItem}"/>
</Grid>
<Window.DataContext>
<l:MainViewModel>
<l:MainViewModel.Menus>
<l:MenuViewModel Name="Pies">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Apple Pie with Pastry Crust" Price="10.50"/>
<l:DetailViewModel Name="Grasshopper" Description="Mint Pie with Oreo Crust" Price="17.95"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
<l:MenuViewModel Name="Juice">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Refreshing Apple Juice" Price="3.70"/>
<l:DetailViewModel Name="Mango" Description="Sweet Mango Juice" Price="4.75"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
</l:MainViewModel.Menus>
</l:MainViewModel>
</Window.DataContext>
</Window>
There are two main components to the above:
Data templates. These tell WPF how to map the view model data structure to elements in the UI. There are two here: a general-purpose one that just always shows, in a TextBlock element, the Name property of any view model data type; and a template that is specific to the DetailsViewModel object, and which displays just the values we're interested in as details.
The UI itself. This is super-simple: two ComboBox elements, providing the drop-down interface to select both a menu and an item on that menu; one ContentPresenter, a control whose main job is just to provide a place to render a data template for a given object; and a Grid object to organize it all. The ComboBox controls explicitly opt in to the data template that displays the item's Name property value, while the ContentPresenter infers the correct data template from the type of view model being used (but it also allows the template to be set explicitly, if you so desire).
The only other thing up there is the DataContext for the window itself, the content of which I've declared in the XAML here just because it's convenient for the purpose of the sample. In your real-world program, which appears to retrieve data from a database, you'd probably have the top-level view model handle populating itself based on that.
(Speaking of the data context: in the above, all binding paths are relative to the top-level view model. For the purpose of the sample, this is more convenient, but you of course have complete control over the data context for any element in the UI. An alternative way to implement this would be to set the DataContext properties for the controls lower in the dependency hierarchy, so that you don't have to repeat the top-level view model's property names in the binding paths.)
And that's all there is to it. You can compile and run the above code, and it will do just what you're asking for your code to do.
Minor notes:
All of the selections start out blank; you can of course initialize them to non-null values if you want, but doing so in the sample above would just add more code for no useful benefit, at least for the purpose of the sample.)
The view models here all use List<T> for their collections. This is fine for the example, because these collections never change. But as you likely already know, real-world WPF programs usually use ObservableCollection<T>, because generally they are including features that allow for those collections to be modified while the program runs. ObservableCollection<T> implements INotifyCollectionChanged, which in turn allows WPF to keep the UI in sync with the bound data. Feel free to replace List<T> with ObservableCollection<T> here or in any other similar scenario.
As promised, here's the code for the NotifyPropertyChangedBase class. There are lots of different ways to implement a base class like this, and in fact I have a different version with a couple more features that I typically use. But this one works well for a basic WPF example (indeed, for many even this one is too "feature-rich" :) ):
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I would like to add pure XAML code into my xaml elements during runtime. Does anyone know how to do that? thank you.
I would like to do something like this: myGrid.innerXAML = stringXAMLcode
that would result to <grid name="myGrid">newgeneratedcodehere</grid>
in PHP you can print verbatim HTML code directly into the HTML file. Is this possible with c#?
if not, can anyone suggest a work-around?
thanks!
There are ways to do what you're asking here, as explained in this CodeProject Article:
Creating WPF Data Templates in Code: The Right Way
However, most of the time you really don't need that for daily operations.
If you're working with WPF, you really need to leave behind the traditional approach from other frameworks and embrace The WPF Mentality.
HTML (4, 5 or whatever) looks like a ridiculous joke when compared to the WPF implementation of XAML, therefore all the horrendous hacks you might be used to in HTML are completely unneeded in WPF, because the latter has a lot of built-in features that help you implement advanced UI capabilities in a really clean way.
WPF is heavily based on DataBinding and promotes a clear and well-defined separation between UI and Data.
For example, this would be what you would do to when you want to "show different pieces of UI" depending on the Data, by using a WPF feature called DataTemplates:
XAML:
<Window x:Class="MyWindow"
...
xmlns:local="clr-namespace:MyNamespace">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Person}">
<!-- this is the UI that will be used for Person -->
<TextBox Text="{Binding LastName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Product}">
<!-- this is the UI that will be used for Product -->
<Grid Background="Red">
<TextBox Text="{Binding ProductName}"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<!-- the UI defined above will be placed here, inside the ContentPresenter -->
<ContentPresenter Content="{Binding Data}"/>
</Grid>
</Window>
Code Behind:
public class MyWindow
{
public MyWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
ViewModel:
public class MyViewModel
{
public DataObjectBase Data {get;set;} //INotifyPropertyChanged is required
}
Data Model:
public class DataObjectBase
{
//.. Whatever members you want to have in the base class for entities.
}
public class Person: DataObjectBase
{
public string LastName {get;set;}
}
public class Product: DataObjectBase
{
public string ProductName {get;set;}
}
Notice how I'm talking about my Data and Business Objects rather than worried about any hacks to manipulate the UI.
Also notice how defining the DataTemplates in XAML files that will get compiled by Visual Studio gives me compile-time checking of my XAML as opposed to putting it together in a string in procedural code, which of course doesn't have any kind of consistency checks.
I strongly suggest you read up on Rachel's answer (linked above) and related blog posts.
WPF Rocks
Why don't you add exactly the elements you want? Something like:
StackPanel p = new StackPanel();
Grid g = new Grid();
TextBlock bl = new TextBlock();
bl.Text = "This is a test";
g.addChildren(bl);
p.addChildren(g);
You can do this for all the elements that exist in the XAML.
Regards
You can use XamlReader to create the UIElement that you can set as child of content control or layout containers:
string myXamlString = "YOUR XAML THAT NEEDED TO BE INSERTED";
XmlReader myXmlReader = XmlReader.Create(myXamlString);
UIElement myElement = (UIElement)XamlReader.Load(myXmlReader);
myGrid.Children.Add(myElement );
I have the following code in my C# WPF MVVM application.
public RelayCommand PolishCommand
{
get
{
polishcommand = new RelayCommand(e =>
{
PolishedWeightCalculatorViewModel model = new PolishedWeightCalculatorViewModel(outcomeIndex, OutcomeSelectedItem.RoughCarats);
PolishedWeightCalculatorView polish = new PolishedWeightCalculatorView(model);
bool? result = polish.ShowDialog();
if (result.HasValue)
{
But i came to know that, calling a window from viewmodel is wrong one in MVVM pattern.
Also stated in the below link.
M-V-VM Design Question. Calling View from ViewModel
Please help me anybody by providing an alternate solution.
Thanks in advance.
You are right that generally you should never access views from view models. Instead in WPF, we set the DataContext property of the view to be an instance of the relating view model. There are a number of ways to do that. The simplest but least correct is to create a new WPF project and put this into the constructor of MainWindow.xaml.cs:
DataContext = this;
In this instance the 'view model' would actually be the code behind for the MainWindow 'view'... but then the view and view model are tied together and this is what we try to avoid by using MVVM.
A better way is to set the relationship in a DataTemplate in the Resources section (I prefer to use App.Resources in App.xaml:
<DataTemplate DataType="{x:Type ViewModels:YourViewModel}">
<Views:YourView />
</DataTemplate>
Now wherever you 'display' a view model in the UI, the relating view will automatically be shown instead.
<ContentControl Content="{Binding ViewModel}" />
A third way is to create an instance of the view model in the Resources section like so:
<Window.Resources>
<ViewModels:YourViewModel x:Key="ViewModel" />
</Window.Resources>
You can then refer to it like so:
<ContentControl Content="{Binding Source={StaticResource ViewModel}}" />
I have answered a very similar question previously, which details how you can open a new window from your view model, whilst maintaining the separation of concerns that the MVVM pattern promotes. I hope this helps: Open a new Window in MVVM
You are allowed to break the rule. You don't have to follow MVVM completely.
I am always using commands to create a new view. You could even create an event (Amagosh, did he just say that!?) for when you click on a button.
I mean, this is just my opinion, I guess it depends on the style programming you're into.