What approach should I use when creating a custom WPF control? - wpf

I'm about to redo an old WinForms application as a WPF application. The core of this applicaton is a custom "grid" component. I'd like some ideas on the best way to do this as a WPF component.
The application displays a grid of data for different countries/sectors. Each cell of the grid displays different information (e.g. graph, image) depending on the available data for that country/sector.
I have a domain model assembly that I want to keep clean - to maximize reuse. The structure is as follows:
Table
Continents
Countries
Sectors
Data[country, sector]
The grid displays countries down the left and sectors across the top.
In the current application, the grid component has a (POCO) Table property and an Refresh() methods to manually redraw it. So, if the Table is updated, the parent of the grid component refreshes it. The grid component also has a number of events that are fired if a continent, country or cell is clicked - so that the parent can response with pop-up menus, etc.
This all works fine.
However, I'm wondering whether this is the correct model to use for a WPF application. Looking at many of the WPF example, they support data-binding, etc. But, it's not clear, from the simple examples, how I might bind a complex object to my components - or whether it would even be worthwhile.
Also, the WinForms component is completely custom drawn - there are no sub-controls (e.g. Labels) in use. Would it be better to use a WPF user control and build the table from a GridLayout and lots of Label, Shape, etc controls? In practice, they are maybe 20 rows and 20 columns in the grid, and the user regular removes and adds countries/sectors (rows/columnms) while using the application.
My immediate goal is to make sure my design plays well in the WPF eco-system, but I have a secondary goal of learning how to do things in a WPFy way - given this is my first WPF app. I'm pretty on top of the use of building a general WPF app - it's just the custom control stuff that remains a little fuzzy (even after reading around it a little).
Any insights/guidance would be appreciated.

You definitely want to adapt the MVVM approach, as outlined by Josh Smith. Practically, this means that your custom grid component will be contained in it's own View. Backing the view will be your ViewModel, where you will define an ObservableCollection of objects containing your data. These objects will probably come from your Model. This interaction is shown below:
Models:
public class TableData
{
public string Country { get; set; }
public string Continent { get; set; }
public object Sector { get; set; }
}
public class TableManager : ITableManager
{
public Collection<TableData> Rows;
public void GetData()
{
this.Rows = new Collection<TableData>();
this.Rows.Add(...
}
}
ViewModel:
public class TableViewModel
{
private ITableManager _tableManager;
public TableViewModel() : base(new TableManager())
{
}
// for dependency injection (recommended)
public TableViewModel(ITableManager tableManager)
{
_tableManager = tableManager;
_tableManager.GetData();
}
public ObservableCollection<TableData> Rows
{
get { return _tableManager.Rows; }
}
}
View:
<ctrls:CustomDataGrid
ItemsSource={Binding Rows}
AutoGenerateColumns=True
>
<!-- Use AutoGenerateColumns if the # of sectors is dynamic -->
<!-- Otherwise, define columns manually, like so: -->
<DataGridTextColumn
Width="*"
Header="SectorA"
Binding="{Binding Country}
/>
</ctrls:CustomDataGrid>
I used CustomDataGrid in the view because I assume you're going to subclass your own DataGrid. This will allow you to override events to customize the DataGrid to your liking:
public class CustomDataGrid : DataGrid
{
public override Event...
}

Related

Caliburn Micro - ActivateItem using Container

I was going through the Caliburn Micro documenation here. Simultaneously, I was trying to put up some rough code for experiment. I am a little confused about how to activate item using a container and how to pass an object to the ViewModel that we are activating.
Lets consider a master/detail scenario. The master contains a list (say datagrid) and the details contain specific row from the master for update(say tab item inside tab control). In the documentation (for ease of understanding), I believe the detail ViewModel was directly instantiated using code like this
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {
int count = 1;
public void OpenTab() {
ActivateItem(new TabViewModel {
DisplayName = "Tab " + count++
});
}
}
So, to apply the above fundamental concept in real world app, we need to instantiate the DetailViewModel (TabViewModel above) using container(say MEF). The challenge then is to know whether the particular DetailViewModel is already opened in the TAB Control. The immediate crude thing that came to my mind was maintaining a List of the Opened Tabs (DetailViewModels). But then we are again referencing DetailViewModel in the MasterViewModel defeating the purpose. Is there any options available to solve this issue.
The second thing that is troubling me is how to pass the Objects from MasterViewModel (Selected Detail Item) to the DetailViewModel. If we use the EventAggregator here then each of the opened DetailViewModels will receive the event which I am not sure how to handle.
If anyone can throw some light on the above two issues, I would be grateful
Update:
The Master is Conductor like this
public class MainViewModel : Conductor<IScreen>.Collection.OneActive, IShell {
....
}
And the detail is defined like this
public class TabViewModel : Screen {
....
}
Both are in the same Window.
I'm not sure exactly what the issue is. In your conductor of many, you have an Items collection provided by Caliburn.Micro. When you come to display a detail view, you can check this collection for the existence of that detail view (using the primary key which you have from the master view).
If the item is already in the Items collection then just activate it (using the ActivateItem method). If the item isn't in the collection, then instantiate it (presumably using a factory if you're using MEF), and add it to the Items collection, and then activate it.

passing data to a mvvm usercontrol

I'm writting a form in WPF/c# with the MVVM pattern and trying to share data with a user control. (Well, the User Controls View Model)
I either need to:
Create a View model in the parents and bind it to the User Control
Bind certain classes with the View Model in the Xaml
Be told that User Controls arn't the way to go with MVVM and be pushed in the correct direction. (I've seen data templates but they didn't seem ideal)
The usercontrol is only being used to make large forms more manageable so I'm not sure if this is the way to go with MVVM, it's just how I would of done it in the past.
I would like to pass a class the VM contruct in the Xaml.
<TabItem Header="Applicants">
<Views:ApplicantTabView>
<UserControl.DataContext>
<ViewModels:ApplicantTabViewModel Client="{Binding Client} />
</UserControl.DataContext>
</Views:ApplicantTabView>
</TabItem>
public ClientComp Client
{
get { return (ClientComp)GetValue(ClientProperty); }
set { SetValue(ClientProperty, value); }
}
public static readonly DependencyProperty ClientProperty = DependencyProperty.Register("Client", typeof(ClientComp),
typeof(ApplicantTabViewModel),
new FrameworkPropertyMetadata
(null));
But I can't seem to get a dependancy property to accept non static content.
This has been an issue for me for a while but assumed I'd find out but have failed so here I am here.
Thanks in advance,
Oli
Oli - it is OK (actually - recommended) to split portions of the View into UserControl, if UI became too big - and independently you can split the view models to sub view models, if VM became too big.
It appears though that you are doing double-instantiations of your sub VM. There is also no need to create Dependency Property in your VM (actually, I think it is wrong).
In your outer VM, just have the ClientComp a regular property. If you don't intend to change it - the setter doesn't even have to fire a property changed event, although it is recommended.
public class OuterVm
{
public ClientComp Client { get; private set; }
// instantiate ClientComp in constructor:
public OuterVm( ) {
Client = new ClientComp( );
}
}
Then, in the XAML, put the ApplicantTabView, and bind its data context:
...
<TabItem Header="Applicants">
<Views:ApplicantTabView DataContext="{Binding Client}" />
</TabItem>
I answered a similar question as yours recently: passing a gridview selected item value to a different ViewModel of different Usercontrol
Essentially setting up a dependency property which allows data from your parent view to persist to your child user control. Abstracting your view into specific user controls and hooking them using dependency properties along with the MVVM pattern is actually quite powerful and recommended for Silverlight/WPF development, especially when unit testing comes into play. Let me know if you'd like any more clarification, hope this helps.

User controls communicating via Commands - how?

I'm working on my first project in WPF/XAML, and there's a lot I've not figured out.
My problem is simple - I need a window that has a bunch of fields at the top, with which the user will enter his selection criteria, a retrieve button, and a data grid. When the user clicks on the button, a query is run, and the results are used to populate the grid.
Now the simple and obvious and wrong way to implement this is to have a single module containing a single window, and have everything contained within it - entry fields, data grid, the works. That kind of mangling of responsibilities makes for an unmaintainable mess.
So what I have is a window that is responsible for little more than layout, that contains two user controls - a criteria control that contains the entry fields and the retrieve button, and a data display control that contains the data grid.
The question is how to get the two talking to each other.
Years back, I would have added a function pointer to the criteria control. The window would have set it to point to a function in the display control, and when the button was clicked, it would have called into the display control, passing the selection criteria.
More recently, I would have added an event to the criteria control. I would have had the window set a handler in the display control to listen to the event, and when the button was clicked, it would have raised the event.
Both of these mechanisms would work, in WPF. But neither is very XAMLish. It looks to me like WPF has provided the ICommand interface specifically to accommodate these kinds of connection issues, but I've not yet really figured out how they are intended to work. And none of the examples I've seen seem to fit my simple scenario.
Can anyone give me some advice on how to fit ICommand to this problem? Or direct me to a decent explanation online?
Thanks!
MVVM is the prevalent pattern used with WPF and Silverlight development. You should have a read up on it.
Essentially, you would have a view model that exposes a command to perform the search. That same view model would also expose properties for each of your criteria fields. The view(s) would then bind to the various properties on the view model:
<TextBox Text="{Binding NameCriteria}"/>
...
<Button Command="{Binding SearchCommand}".../>
...
<DataGrid ItemsSource="{Binding Results}"/>
Where your view model would look something like:
public class MyViewModel : ViewModel
{
private readonly ICommand searchCommand;
private string nameCriteria;
public MyViewModel()
{
this.searchCommand = new DelegateCommand(this.OnSearch, this.CanSearch);
}
public ICommand SearchCommand
{
get { return this.searchCommand; }
}
public string NameCriteria
{
get { return this.nameCriteria; }
set
{
if (this.nameCriteria != value)
{
this.nameCriteria = value;
this.OnPropertyChanged(() => this.NameCriteria);
}
}
}
private void OnSearch()
{
// search logic, do in background with BackgroundWorker or TPL, then set Results property when done (omitted for brevity)
}
private bool CanSearch()
{
// whatever pre-conditions to searching you want here
return !string.IsEmpty(this.NameCriteria);
}
}

WPF Custom Control

I have a view with some controls and a viewbox. At the moment in the viewbox is a grid and a graphic at the bottom. The application reads a XML-file and refreshs the content of the grid and the graphic for every node in the XML-file.
Now the application not only have to show a grid with a graphic. Depending on the XML-node, the application should show a grid with a graphic like now or a grahpic and at the bottom 2 lines of text.
You explained what your application is, but you didn't ask a question. Please state the question, and I can try to give you a better answer.
My best guess is "how do I do that? Do I need to write a custom control?" If that is what you are asking, you probably don't.
Usually you don't need a custom control to make a specialized view for a listbox, listview, or gridview. You can often use data templates, control templates or styles to achieve what you are looking for.
I am not sure if this is a good resource, but the XAML looks like it may be a good starting point for learning how to do control templates:
http://ligao101.wordpress.com/2007/07/27/customizing-listview-in-wpf-part-i/
Simply googling for any of those terms ("ListView data template", etc) will probably get you some good information.
Edit:
From the comments, you are trying to support one of two types of data in the same space in the UI, depending on what is in your XML file:
Image Only
Image, plus two lines of text
One way to solve this would be to create a view model for your XML items, and bind the items to those view models:
public class XmlItemViewModel // Call this something more appropriate to your app
{
public Visibility TextVisibility { get; set; }
public string Text1 { get; set; }
public string Text2 { get; set; }
public Image Picture { get; set; }
}
If you already have a different class that has this data, keep it, and make the view model read properties off that class.
Bind the XAML TextBlock Visibility property to "{Binding TextVisibility}", and it should work. If you set the viewmodel property "Visibility.Collapsed", the text blocks will go away, and your ViewBox should shrink to fit only the image.
If you do this, you don't need a custom control, just a custom ViewModel class.
If it doesn't seem to collapse correctly, you could wrap your image and text blocks with a StackPanel or WrapPanel.

Silverlight MVVM MEF ViewInjection

since my title is buzzword compliant I hope I will get lots of answers to my question or any pointers to the right direction.
OK what I usually do is have a ViewModel which contains a list of ViewModels itself.
public class MasterViewModel
{
public ObservableCollection<DetailViewModel> DetailViewModels { get; set; }
public DetailViewModel Detail { get; set; }
}
<ItemsControl ItemsSource="{Binding DetailViewModels}">
<ItemsControl>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:DetailsView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
With this in mind I will now come to my questions. I read a lot of good things about MEF and also saw the dashboard sample of Glenn Block but this was not helping me enough.
What I want to do is sidbar (like the windows sidebar).
Sidebar = StackPanel
ListItems = Gadget
ButI want it MVVM style
OK I have something like a contract
IGadget
I implemented an custom Export.
[ExportGadget(GadgetType = GadgetTypes.News)]
I have my NewsGadgetView.xaml (which implements IGadget) and imports the NewsGadgetViewModel and also makes itself available as ExportGadget.
so far so good. With this I can create a set of gadgets.
Then I have my SidbarView.xaml which imports a sidebarViewModel.
and now I get lost...
I thought of something like a GadgetFactory which uses PartCreator to create my Gadgets.
but this would sit in my SidebarView.xaml
But I want to have control over my Gadgets to add and remove them from my sidebar.
So I thought about something like an ObserveableCollection...
Which I bind to
The GadgetHost is basicaly Grid which will dynamicaly load the Gadget....
So how would I create my sidebar containing different gadgets without knowing which Gadgets are available and have a ViewModel for the sidebar as well as for each gadget?...
Thanks for any help....
This is where the power of the Managed Extensibility Framework comes in. I basically have the same challenge with an existing project.
My resolution was to abstract the views and regions, and then use a routing mechanism.
Basically, there is a custom export for a region, and I export a FrameworkElement (might be a StackPanel, Grid, etc etc), The views have a set of attributes, those are exported as UserControl.
A manager handles the imports using a lazy import collection. It simply assigns these to a dictionary so we have view enums mapping to instances of views, then region enums mapping to instances of the regions.
The route table then waits for a request to activate a view (this may happen on load), and finds the route from the view to the region, then inserts it.
What about the view model?
For "global" information I'm using a contract that is exported, like this:
[Export(typeof(IMasterViewModel))]
public class MasterViewModel
{
}
That has something every plugin may need. Then I have a base view model the "child" view models inherit from:
public class BaseViewModel
{
[Import(typeof(IMasterViewModel))]
public MasterViewModel MasterVM { get; set; }
}
So now let's say I have a completely separate XAP. I will need to reference some "common" interfaces. So I'm not referencing an instance of the global view model, just the contract. However, within my plugin XAP, I can do this:
[Export]
public class PluginViewModel : BaseViewModel
{
etc ... etc ..
}
public partial class PluginControl : UserControl
{
[Import]
public PluginViewModel
{
get { return LayoutRoot.DataContext as PluginViewModel; }
set { LayoutRoot.DataContext = value;
}
}
When the view model is imported to the view, it will also import the master view model, providing access to those other parts. If you need to trigger some action when the view model is available, simply implement IPartImportsSatisfiedNotification and you can fire when ready.

Resources