I would like to design a light "members listing/editing" app in one and only window.
My guess was that the best and simplest way to achieve this would be to have the "listing" part (mostly a datagridview and some search stuff) on a panel, and the "editing" (new member or modify member) on another, each panel hiding the other depending on what User wants to do.
That's what I have to end up with, visually speaking.
I've thought of many ways to design this but no one sounded actually good to me, mainly when it comes to instantiate the viewmodel of the editing panel passing the selected member in the dgv of the listing panel or stuff like that.
I still consider myself a beginner at WPF and I'm sure the most clever solution is something that didn't come to my mind.
Can't wait to read expert's suggestions ;)
You should be thinking more in terms of DataTemplate.
Split your two different views up, eg. MemberListingView.XAML and MemberEditView.XAML. Create view-models for each view.
To put it all together, follow the data templating technique:
<DataTemplate DataType="{x:Type vm:MemberListingVM}">
<AdornerDecorator>
<views:MemberListingView />
</AdornerDecorator>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MemberEditVM}">
<AdornerDecorator>
<views:MemberEditView />
</AdornerDecorator>
</DataTemplate>
// Now use a content presenter
<ContentPresenter Content="{Binding CurrentView}" />
You should have somewhere in your context a property that specifies the current view that you need to show.
private ViewModelBase _currentView;
public ViewModelBase CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
RaisePropertyChanged("CurrentView");
}
}
// ...
public void OnSelectedMemberChanged(Member member)
{
// Depending on your logic
// If some condition...
CurrentView = new MemberEditVM(member);
// else
CurrentView = MemberListingVM;
}
Related
I have a WPF control whose content completely depends on a property of its data context. For the sake of this, let's just say the control's DataContext is of type Product, which has a Status property of InStock, OutOfStock, or Discontinued.
I have individual user controls for each of those status types. I could, and have, created some kind of panel that binds the visiblity of each to Product.Status. But that created problems, since some of the user controls ended up with funky stuff because some depend on various properties being set. And in my actual application, there are many statuses, so the visualtree gets too big for my taste.
I solved the problem by creating and in my code, I check for a status change on the DataContext and set the appropriate child in a big switch statement. I would like to do this in XAML if possible. I want the child to be set on demand, so I assume I'll need to use templates. Something like this:
SwitchControl would derive from Decorator or Border, whatever.
<SwitchControl Property="Status">
<SwitchControl.Possibilities>
<Possibility Value="Discontinued">
<Possibility.Template>
<DiscontinuedView />
</Possibility.Template>
</Possibility>
<Possibility Value="InStock">
<Possibility.Template>
<InStockView />
</Possibility.Template>
</Possibility>
<SwitchControl.Possibilities />
</SwitchControl>
It would be even better if I could shorten the whole thing to:
<SwitchControl>
<Possibility Value="Discontinued">
<DiscontinuedView />
</Possibility>
<Possibility Value="InStock">
<InStockView />
</Possibility>
</SwitchControl>
Point being, only one child would exist at any given time. Anyone know of a way to get this done? I looked around in MVVM frameworks and couldn't find anything. Otherwise I'll experiment with creating a custom control myself.
You might want to take a look at the DataTemplateSelector class. This allows you to define templates based on different criteria, e.g. the type of the current DataContext. An example could look somewhat like the following:
public class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DiscontinuedDataTemplate { get; set; }
public DataTemplate InStockDataTemplate { get; set; }
public DataTemplate OutOfStockDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var product = item as Product;
switch (product.Status)
{
case Status.InStock:
return InStockDataTemplate;
case Status.Discontinued:
return DiscontinuedDataTemplate;
case Status.OutOfStock:
return OutOfStockDataTemplate;
}
// Fallback
return DiscontinuedDataTemplate;
}
}
...and use it in the following way:
<Window.Resources>
<DataTemplate x:Key="DiscontinuedDataTemplate">
<DiscontinuedView />
</DataTemplate>
<DataTemplate x:Key="InStockDataTemplate">
<InStockView />
</DataTemplate>
<DataTemplate x:Key="OutOfStockDataTemplate">
<OutOfStockView />
</DataTemplate>
<!-- DataTemplate Selector -->
<local:MyDataTemplateSelector x:Key="MyTemplateSelector"
DiscontinuedDataTemplate="{StaticResource DiscontinuedDataTemplate}"
InStockDataTemplate="{StaticResource InStockDataTemplate}"
OutOfStockDataTemplate="{StaticResource OutOfStockDataTemplate}"/>
</Window.Resources>
<ContentControl ContentTemplateSelector="{StaticResource MyTemplateSelector}" Content="{Binding Product}"/>
Thanks for the suggestion andreask. I ended up creating a control that I think solves the problem more directly. I've been working on a WPF helper library that I'll post to nuget in the future, but if you want to use it now, it's at:
https://gist.github.com/StevePotter/b17f8d4b2657a2d2610390a11fb57e03
Example XAML is included. I hope this is useful for someone!
I have an app written in WPF (MVVM), which based on some conditions, will create instances of different UserControls, These UserControls are completely independent, used to display certain information. They have some custom logic inside, like timers and so on, so I can't use Templates.
Now I face the problem that I want to create a list of UserControls in the ViewModel, and bind the host UI to it. The problem is that I don't know how to bind and what to bind. In a non MVVM project, you would simply get the layout where you want to put your controls, and add them there as children. In MVVM app, I don't know how to do this. I imagine having a WrapPanel with ItemsSource, that will add all the controls and resize itself as needed, based on the UserControls.
Can someone suggest a solution?
EDIT:
My ViewModel exposes an ObservableCollection of IMyDriver right now. So that's what I thought, to break a little bit MVVM to get what I describe next:
Now, Each IMyDriver can be a different type of driver, and can implement different other interfaces. I need the UI to create specific UserControls that know how to get maximum from these Drivers, based on their capabilities. In short, the UserControls connect to the device through the driver for polling data. And each UserControl does it in a specific way.
You can do it quite simply and easily by declaring specific data type classes for the data in each UserControl and define DataTemplates that expose your UserControls in the App.xaml file:
<DataTemplate DataType="{x:Type YourViewModelsPrefix:YourViewModel">
<YourViewsPrefix:YourView />
</DataTemplate>
<DataTemplate DataType="{x:Type YourViewModelsPrefix:YourOtherViewModel">
<YourViewsPrefix:YourOtherView />
</DataTemplate>
<DataTemplate DataType="{x:Type YourViewModelsPrefix:AnotherViewModel">
<YourViewsPrefix:AnotherView />
</DataTemplate>
Now whenever the Framework comes across an instance of these view model classes, it will render the associated view/UserControl. You can display them by having a property of the type of your view model using a ContentControl like this:
<ContentControl Content="{Binding YourViewModelProperty}" />
...
public YourBaseViewModelClass YourViewModelProperty { get; set; }
Make sure that all of your view models extend this class:
public YourViewModel : YourBaseViewModelClass { }
...
public AnotherViewModel : YourBaseViewModelClass { }
Then you can swap each view model (and display each related view) like this:
YourViewModelProperty = new AnotherViewModel();
Based on what Will commented, and what Sheridan answered, I have found the solution to my problem.
So:
I don't break MVVM by leaving ViewModel types intact.
I create DataTemplates in my Window's Resources tag, and in each data template, I assign the DataTemplate to be my UserControl defined in another assembly (UICommons)
<DataTemplate x:Key="IMultiChannelMeasurementDCDataTemplate">
<uicommon:MeasurementMax8ChannelMonitoringUserControl/>
</DataTemplate>
I create a Template Selector in my application assembly, and based on the interfaces the DataTypes implement, I return the right DataTemplate, that I assign in the same Window's Resources tag
<!-- DataTemplate Selector -->
<local:DriverComponentDataTemplateSelector x:Key="templateSelector"
DefaultDCDataTemplate="{StaticResource DefaultDCDataTemplate}"
IIhcDCDataTemplate="{StaticResource IIhcDCDataTemplate}"
IMultiChannelMeasurementDCDataTemplate="{StaticResource IMultiChannelMeasurementDCDataTemplate}"
IProgrammablePowerSourceDCDataTemplate="{StaticResource IProgrammablePowerSourceDCDataTemplate}"
IEnvDCDataTemplate="{StaticResource IEnvDCDataTemplate}"/>
I create an ItemsControl in the Window, with the following XAML code, that binds itself to my ObservableCollection of items
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemTemplateSelector="{StaticResource templateSelector}" ItemsSource="{Binding DriverComponentsInfo}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" x:Name="ucWrapPanel">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
I enjoy dynamically created UserControls based on different Drivers!
P.S. I upvoted Will's comment and Sheridan's answer, because without these, I wouldn't be able to find the solution. Thx!
They have some custom logic inside, like timers and so on, so I can't use Templates.
This does not follow. I think you may have a misconception about the capabilities of WPF.
Also, as you want to use MVVM: Binding to a list of UserControls is breaking the pattern. View-models should only ever reference other view-models (and models); they do not know anything about the UI. Bind to a collection of view-models which have associated UserControls as their views (consider using implicit DataTemplates). To bind a WrapPanel you use an ItemsControl and set its ItemsPanel accordingly.
I am developing an Application with the following layout,
I have no knowledge of MVVM and I'm on a tight timeline.
The Red area on the right is a ContentControl.
The area on the left will be a sidebar for Navigation
My question is
should I follow this aproach
http://channs.blogspot.com/2010/09/wpf-navigation.html OR
should I use MVVM light / Prism OR
should I use WPF pages?
I am currently inclined to option 1. Are there any pros and cons to it?
Which option would you recommend ?
My application will always have only one developer i.e. Me and it will contain about 30 screens.
Since you're on a tight schedule and have no knowledge of MVVM (or Prism?), then you'll be way better off using options 3 and 1. From my experience, it takes devs a long time to get up to speed on MVVM and Prism. Certain things that are taken for granted, become much more difficult in the MVVM/Prism world.
With that being said, I'm a huge advocate of MVVM/Prism and feel that it's well worth the extra effort, especially for a project of your size. However, since you're pressured for time, don't bother, just do code-behind.
Prism takes time to learn, if you are on a tight deadline then I think you should just go with what you know, When you have time reading the prism book especially the section on navigation will be helpful.
Don't go with PRISM,that would be an overkill for such non complex apps. If you are familiar with DataBinding, that can save you a lot of time even without MVVM.Since you're in a hurry, I think you should go with what you already know and start learning MVVM at ease.Good luck
mvvm is not that hard. in your case you need first a mainviewmodel.
public class MainViewModel
{
private ICollectionView _myView {get;set;}
public ObservableCollection<MyModulWrapper> MyModules{get;set;}
public MyModulWrapper SelectedModul {get;set;}
public MainViewModel()
{
this.MyModules = new ObservableCollection<MyModulWrapper>();
//i use icollectionview because i often need sorting or filtering
this._myView = = CollectionViewSource.GetDefaultView(this.MyModules);
this._myView .CurrentChanged += (s, e) => { this.SelectedModul = this._myView .CurrentItem as MyModulWrapper; };
}
}
you have to fill(in any way - i use mef for my apps, but hardcoded its also ok) your collection with all modules(viewmodels) you wanna show on your top screen.
the MyModulWrapper just contain the viewmodel for your modul and a nice display name for your navigation.
public class MyModulWrapper
{
public string Displayname {get;set;}
public object Modul {get;set;}//instead of object you can take an interface or base class or whatever
}
now you have all to let the mainview run :) you just have to set the datacontext for the MainWindow to your MainViewModel.
mainwindow.xaml
<Window.Resources>
<!--for each viewmodel you wanna show create a datatemplate. so wpf knows how to render your viewmodel-->
<DataTemplate DataType={x:Type local:MyViewmodel4FirstButton>
<local:MyFirstButtonView />
</DataTemplate>
</Window.Resources>
<!-- for navigation -->
<ListBox ItemsSource="{Binding MyModules}"
SelectedItem="{Binding SelectedModul , Mode=OneWay}"
IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Displayname}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- all you need to show your selected modul - if you have a DATATEMPLATEs!! -->
<ContentControl Content="{Binding SelectedModul }"/>
all your viewmodels have to implement INotifyPropertyChanged of course, and raise it properly.
ps: code is written without IDE so ignore errors^^
We want to create a subclass of Canvas that only allows children of a specific type (they need to have intimate knowledge of our subclass and vice-versa.) That said, is there any way to force a panel to only accept children of a certain type (or types)?
M
The solution we came up with was to simply subclass the Canvas, then monitor the children. If one is added that's not of the type we want, we instantly remove it and throw an error. Won't stop compile-time errors but does the trick.
Extending this further I was thinking about also subclassing the canvas, then Newing over the Children property to return our own collection which we've internally synced to the panel's children via binding. That way we can also have compile-time support. Granted if someone casts our subclass to a straight canvas, then obviously the 'new'd Children property won't be accessed (its a 'new' not an override) but the aforementioned collection monitoring will still give us what we want.
It would have been nice if the WPF team had come up with a generic canvas so we could do something like canvas but that obviously wouldn't work in XAML unless they somehow came up with syntax for that. Then again, a canvas is pretty damn basic so maybe we'll just roll our own geeneric version where we could do something like this...
public class TypedCanvas<t> : PanelBase
{
// Implementation here
}
public class FooCanvas : TypedCanvas<Foo>{}
public class LaaCanvas : TypedCanvas<Laa>{}
...of which we could then use FooCanvas and LaaCanvas via XAML while still getting all the benefits of using generics.
Even better, make it TypedPanelBase so we could use it with any other custom panel as the base type.
Actually, now that I've typed this... I think I'm about to go re-write our canvas to try this approach! (Either way, I now have a solution which is what we were after.)
Actually... no way.
Besides, I don't understand your goals. If you need to work with some specific containers just cast Panel.InternalChildren:
this.InternalChildren.OfType<MyType>().Do(...);
Consider about scenario: you have a collection of strings, which is the source for ItemsControl. In DataTemplate we have button which content is binded to item from mentioned collection. And ItemsControl.ItemsPanel is Canvas.
public IEnumerable<string> Items
{
get;
}
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
So, what items types do you want to restrict? Buttons or strings?
The problem in this scenario is that ContentPresenters will be effective visual children of Canvas. But in overriden method OnVisualChildrenChanged (where you could try to check item type) Content and ContentTemplate properties are set to null due to deferred binding.
So the one acceptable solution I can propose is creating your own ItemsControl, which returns some concrete container instead of ContentPresenter:
public class MyItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new Button();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is Button;
}
}
<self:MyItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<self:MyPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</self:MyItemsControl>
With this approach, you guarantee that your item containers (Panel.InternalChilder) are buttons (or something) and in MyPanel you could safely cast:
this.InternalChildren.Cast<Button>()
In our product, we use MVVM model first approach and it works nicely but with one caveat. When view becomes complex it takes time to create it from the data template. If the view is shown and hidden frequently, it becomes slightly irritating. If using view first, it would be easy enough to cache a view if needed - but when using DataTemplate and model first, we do not have much control of view creation.
Anybody solved this problem already without switching to the view first method?
Works beautifully if using the #blindmeis idea.
The overall recipe:
Create a ContentControl or UserControl named ViewCache:
public partial class ViewCache
{
public ViewCache()
{
InitializeComponent();
Unloaded += ViewCache_Unloaded;
}
void ViewCache_Unloaded(object sender, RoutedEventArgs e)
{
Content = null;
}
private Type _contentType;
public Type ContentType
{
get { return _contentType; }
set
{
_contentType = value;
Content = ViewFactory.View(value); // use you favorite factory
}
}
}
In the DataTemplate, use the ViewCache, pass the type of the real view you want to use:
<Window.Resources>
<DataTemplate DataType="{x:Type TestViewCache:Foo}">
<TestViewCache:ViewCache ContentType="{x:Type TestViewCache:View }"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentPresenter Height="200" Width="300"
Content="{Binding ViewModel}"/>
<Button Content="Set VM" Click="SetVMClick"/>
<Button Content="UnSet VM" Click="UnSetVMClick"/>
</StackPanel>
with viewmodel first approach i think you have no chance to "cache" the view. so you may consider to use view first and a viewmodel locator for the heavyweight datatemplates workflows. here is a solution when using datatemplates with lists.
but maybe there is any solution with overriding the wpf datatemplate mechanism?
edit: what if you create just a "markerview" for your viewmodel, so wpf datatemplate can find it. and then within this marker view you create/rehydrate the real view? something like an view service locator?