I have my SoapBox.Document 'Register'
[Export(SoapBox.Core.ExtensionPoints.Workbench.Documents, typeof(IDocument))]
[Export(CompositionPoints.Workbench.Documents.Register, typeof(Register))]
[Document(Name = DOC_NAME)]
class Register : AbstractDocument
{
public Receipt actualReceipt;
private const string DOC_NAME = "Register";
public Register()
{
Name = DOC_NAME;
Title = "Recipe Document Title";
SomeProperty = "Hello from the recipe document!";
}
}
In this Document I want to user UserControls witch are kind of a own "View"
Like a ListView for all ReceiptPositions
So now I got my Model Receipt and ReceiptPosition
Model Receipt
class Receipt
{
public int Id { get; set; }
public string Receiptnumber { get; set; }
public IList<ReceiptPositions> ReceiptPositions { get; set; }
and Model ReceiptPosition
class ReceiptPosition
{
public int Id { get; set; }
//public Receipt Receipt { get; set; } using for Database
public int Position { get; set; }
public string Article { get; set; }
}
So now I want to add a UserControl witch displays a List of all articles in ReceiptPositions.
But how do I bind the data so that when a new ReceiptPosition gets added to the IList in Receipt the UserControl get 'refreshed' automatically?
Here is a visual example of whatI need..
Host with Data and two PLugins wich each show the same Data but in a different way.
You can use an ItemsControl for this purpose.
xaml:
<ItemsControl ItemsSource="{Binding MyReceipt.ReceiptPositions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Where you put your view -->
<TextBox Text="{Binding Article}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- Can be whatever Panel type you want -->
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
cs:
private Receipt _myReceipt;
public Receipt MyReceipt { get { return _myReceipt; } set { _myReceipt = value; OnPropertyChanged("MyReceipt"); } }
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyReceipt = new Receipt { ReceiptPositions = new ObservableCollection<ReceiptPosition>() };
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Foo" });
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Bar" });
MyReceipt.ReceiptPositions.Add(new ReceiptPosition { Article = "Baz" });
MyReceipt.ReceiptPositions[0].Article = "Frabazamataz";
}
Explanation:
The ItemsControl allows you to bind a list to its ItemsSource Property to use as the DataContext to each view created by the DataTemplate.
The Observable Collection gives PropertyChange notifications automatically with each item added, removed, or changed.
This allows you to have a very flexible list of items based solely on your data.
Death's answer is correct, i.e. you use DataTemplates. If your views and data templates are in a MEF plugin then you need to import both the plugins and the data templates that map the view models to the views. In the other question you posted about this it was obvious that you're trying to export your plugin user controls...personally I think this is a bit misguided. If your main application is using MVVM then your plugins should as well. In this case your plugins should export an IPlugin class and also specify a DataTemplate that maps it to a view. As I indicated on the other page, the data template must be imported as well so that you can add it to the global resources.
I've created a project that shows this in action using the classes you provided in your uother question, you can download it here. The main points to look at are the data templates in the plugin project and the two places where things are imported in the main project.
Note that in my demo I'm requiring each plugin to explicitly specify a DataTemplate for its view and view model, but you may not want to do this so I've also added a chunk of commented-out code at the bottom of App.xaml.cs that shows how to avoid that (to make it work I had to add the view type to the IPlugData class, but that's only needed for this one example). If you choose to create the DataTemplates manually then the plugins don't need to specify the data templates and they also don't need the custom ResourceDictionary that holds them.
If you have an questions feel free to post back here in the comments.
Related
I'm looking to build a Scatter plot with a best fit line using Live Charts.
Now the way I've been doing this is by having a SeriesCollection on the main view model like so, within the class of this interface I manually add the BubbleSeries and the LineSeries for the chart:
public interface IPointAnalysisViewModel : IAnalysisViewModel
{
SeriesCollection Series
{
get;
}
}
PointAnalysisViewModel:
foreach (var pointSliceViewModel in this.slices)
{
this.series.Add(pointSliceViewModel.Series);
}
this.bestFitLineSeries = this.BuildBestFitLine(pointAnalysisModel.BestFitValues);
this.series.Add(this.bestFitLineSeries);
This series is then bound within xaml to the Series of the CartesianChart as follows:
<wpf:CartesianChart Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Series="{Binding Path=Series}" >
... UI Fluff
</wpf:CartesianChart>
Pretty basic as you'd expect, but this means that in my ViewModels for the series I then have know that the view will display a bubble series for me as follow:
public interface IPointSliceViewModel : IViewModel
{
IBubbleSeriesView Series
{
get;
}
bool Show
{
get;
set;
}
}
To me this looks badly like some code 'smell' in that I need to know view specific objects within my View Model.
The only way that I can think that would allow me to separate the concerns of the View and the ViewModel is if I were able to provide the CartesianChart with the data templates for the Series and bind the Series to a collection of IPointSliceViewModels instead as follows:
<wpf:CartesianChart Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SeriesSource="{Binding Path=Series}">
<wpf:CartesianChart.SeriesTemplates>
<DataTemplate DataType={x:Type local:PointSliceViewModel}>
<wpf:BubbleSeries .../>
</DataTemplate>
</wpf:CartesianChart.SeriesTemplates>
... UI Fluff
</wpf:CartesianChart>
Which would simply allow me to have:
public interface IPointAnalysisViewModel : IAnalysisViewModel
{
ObservableCollection<IPointSliceViewModel> Series
{
get;
}
}
and:
public interface IPointSliceViewModel : IViewModel
{
ChartValues<ObservablePoint> Values
{
get;
}
bool Show
{
get;
set;
}
}
Is it currently possible to provide the DataTemplates for the Series or do I have to manually do this in the code behind at the moment?
Or is there a different way of having potentially limitless numbers of series in a chart without having to define each of them in the View and without having to keep track of the view specific details in the VM?
A list of products is used through a WPF application. The list lstProducts is created in the business tier of the application. The list is fairly stable over time ... products only updated every 6 months.
How can that list be instantiated in C# such that it is available throughout the application?
My C# Class creates a list
namespace BusinessObjects
{
public class Products
{
public class Product
{
public Int64 ProductId { get; set; }
public string FileAs { get; set; }
}
public List<Product> lstFileAs { get; set; }
public Products()
{
//populate lstFileAs
}
}
}
ComboBoxes on various forms are databound as follows
products = new Products());
cboProducts.DataContext = products;
cboProducts.ItemsSource = products.lstFileAs;
cboCustomer.DisplayMemberPath = "FileAs";
Let us please consider it as read that we all prefer to avoid global variables. However we are putting a new front end on an old and widely used application which does use global variables. The old application is written in VB6 and runs well in spite of using global variables. We are instructed to make the minimum changes to avoid unnecessarily introducing bugs.
You can create an ObjectDataProvider and CollectionViewSource in App.xaml (for instance) and reference that in your project. I demonstrate here a possible implementation. This code assumes you create a GetProducts() method.
App.xaml:
<ObjectDataProvider x:Key="ProductsObjDataProvider"
ObjectType="{x:Type BusinessObjects:Products}"
MethodName="GetProducts">
</ObjectDataProvider>
<CollectionViewSource x:Key="ProductsView" Source="{Binding Source={StaticResource ProductsObjDataProvider}}"/>
To bind a combobox:
<ComboBox Name="cboProducts"
DisplayMemberPath="FileAs"
ItemsSource="{Binding Source={StaticResource ProductsView}}"
SelectedValue="{Binding Path=ProductID, Mode=TwoWay}"/>
I am programming a Windows 8.1 App using C#/XAML as well as the MVVM-Light Toolkit.
In my program there is a Schedule that consists of 3 components:
a GridView with 5 elements for Monday, Tuesday, ....
a ListView with x elements each showing the start- and end-time of
the current period. x depends on the number of period the user chose
for his schedule to have.
a GridView with 5*x elements that represent the places for the events
set by the user.
These 3 components are again inside a FlipView to enable multiple Schedules.
I enabled this in code via the following objects:
public class Schedule
{
public int WeekNumber { get; set; }
public ScheduleComponentSettings ScheduleComponentSettings { get; set; }
public ScheduleComponents ScheduleComponents { get; set; }
}
public class ScheduleComponents
{
public ObservableCollection<WeekDay> WeekDayItems { get; set; }
public ObservableCollection<FreePeriod> FreePeriodItems { get; set; }
public ObservableCollection<PeriodTime> PeriodTimesItems { get; set; }
public ObservableCollection<LessonTime> LessonTimesItems { get; set; }
}
In my ViewModel I have an ObservableCollection of the Schedule class:
public ObservableCollection<Schedule> ScheduleComponentsList
{
get
{
return _ScheduleComponentsList;
}
set
{
if (_ScheduleComponentsList == value)
{
return;
}
RaisePropertyChanging(ScheduleWeekListPropertyName);
_ScheduleComponentsList = value;
RaisePropertyChanged(ScheduleWeekListPropertyName);
}
}
The FlipView and its elements bind to that as follows (this is ofc. shortened to show only the ItemsSources):
<FlipView
ItemsSource="{Binding Main.ScheduleComponentsList, Source={StaticResource Locator}}"
<FlipView.ItemTemplate>
<DataTemplate>
<GridView
ItemsSource="{Binding ScheduleComponents.WeekDayItems}"/>
<ListView
ItemsSource="{Binding ScheduleComponents.PeriodTimesItems}"/>
<GridView
ItemsSource="{Binding ScheduleComponents.FreePeriodItems}"/>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Now here is the problem:
When I change a property on an element inside the FreePeriodItems or even when I replace the collection completely, the View only updates when I reload the entire Page. Same for all the other properties I update in one on the ScheduleComponents.
This does not occur however when I change the ScheduleComponentList itself. When I add items to it for example they are automatically being updated in the view.
Now I am sitting on the problem for ages now.
This does not occur however when I change the ScheduleComponentList itself. When I add items to it for example they are automatically being updated in the view.
That's exactly how ObservableCollection<T> works. It raises events only when the list itself changes, when you add or remove elements:
Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
from ObservableCollection Class
To make it work implement INotifyPropertyChanged on your WeekDay, FreePeriod, PeriodTime and LessonTime. When it's done you'll get event not only when collection changes but also when any of the items that already are part of collection is modified.
I´m new to PRISM and trying some things out. I´m struggeling a little bit with MVVM.
The way you "connect" a view with a viewmodel is clear:
injection through unity, or
set the data context manually (ServiceLocator)
Everything is working fine if I add a view to a region (the viewmodel is created automatically). But that´s not the use case.
Let´s take a look at the example:
public class MyViewModel : NotificationObject
{
public ObservableCollection<AnotherViewModel> OrderModel { get; private set; }
}
I have to create view models and add them to the collection. This view models have to be displayed (AnotherView) in a region (OrderRegion). My problem is, how can I achieve that now the view is created when I am adding a viewmodel to a region. This region is a TabControl, so it could happened that different views must be displayed.
I have already took a look to the PRISM Quickstarts and the StockTrader example. What I am looking for is quite similar to
virtual protected void StartOrder(string tickerSymbol, TransactionType transactionType)
{
if (String.IsNullOrEmpty(tickerSymbol))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.StringCannotBeNullOrEmpty, "tickerSymbol"));
}
this.ShowOrdersView();
IRegion ordersRegion = _regionManager.Regions[RegionNames.OrdersRegion];
var orderCompositeViewModel = ServiceLocator.Current.GetInstance<IOrderCompositeViewModel>();
orderCompositeViewModel.TransactionInfo = new TransactionInfo(tickerSymbol, transactionType);
orderCompositeViewModel.CloseViewRequested += delegate
{
OrderModels.Remove(orderCompositeViewModel);
commandProxy.SubmitAllOrdersCommand.UnregisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelAllOrdersCommand.UnregisterCommand(orderCompositeViewModel.CancelCommand);
commandProxy.SubmitOrderCommand.UnregisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelOrderCommand.UnregisterCommand(orderCompositeViewModel.CancelCommand);
ordersRegion.Remove(orderCompositeViewModel);
if (ordersRegion.Views.Count() == 0)
{
this.RemoveOrdersView();
}
};
ordersRegion.Add(orderCompositeViewModel);
OrderModels.Add(orderCompositeViewModel);
commandProxy.SubmitAllOrdersCommand.RegisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelAllOrdersCommand.RegisterCommand(orderCompositeViewModel.CancelCommand);
commandProxy.SubmitOrderCommand.RegisterCommand(orderCompositeViewModel.SubmitCommand);
commandProxy.CancelOrderCommand.RegisterCommand(orderCompositeViewModel.CancelCommand);
ordersRegion.Activate(orderCompositeViewModel);
}
The view model is created inside the code and added to the region. The whole type regestring is happend through the "ViewExportAttribute" so it makes it harder to understand the pattern behind it.
EDIT:
I have found a way to do this manually but it´s not very nice:
var view = (FrameworkElement) ServiceLocator.Current.GetInstance<AnotherView>();
var model = ServiceLocator.Current.GetInstance<AnotherViewModel>();
view.DataContext = model;
regionManager.Regions["OrderRegion"].Add(view, null, true);
regionManager.Regions["OrderRegion"].Activate(view);
Roman
EDIT2:
Hi, I´m sorry, maybe I wasn´t clear enough.
My goal was to create a view model and then to configure it like in the example from the StockTrader above: Subscribe events, commands etc. After that I want to add this view model to the region, so that´s it could be displayed. This region might be a tabcontrol where different views with different view models are displayed.
The order is:
Create a view model in the controller class
Configure the view model
Add the view model to local collection inside the controller
Add the view model to the region
The missing piece what I was looking for was how to make it happen, that the view is created “automatically” with all stuff like binding etc. I have found an approach in this article (http://www.codeproject.com/Articles/229931/Understand-MVVM-Using-PRISM-by-Hello-World-Silverl). I have to create own interfaces for the view and the view model (IAnotherViewModel, IAnotherView).
Another approach can be found here: http://paulstovell.com/blog/viewmodel-first-prism
Is there any reason not to use implicit DataTemplates for this?
They are DataTemplates that define a DataType property, but not a Key property, and they are used anytime WPF tries to draw an object of the specified DataType
For example,
<TabControl ItemsSource="{Binding MyViewModelCollection}"
SelectedItem="{Binding SelectedViewModel}">
<!-- This could also go elsewhere, like Application.Resources -->
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</TabControl.Resources>
</TabControl>
If the TabControl is displaying an object of type ViewModelA, it will draw it using ViewA, and if it's displaying ViewModelB, it will draw it using ViewB
yI you are using MEF then you can automate your view registration using Attributes:
/*YOUR VIEW*/
[ExportViewToRegion("MyView", "MyRegion")]
[Export(typeof(MyView))]
public partial class MyView : UserControl
{
....
}
/*IMPLEMENTATION*/
public interface IExportViewToRegionMetadata
{
string ViewName { get; }
string TargetRegion { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false)]
public class ExportViewToRegionAttribute : ExportAttribute
{
public ExportViewToRegionAttribute(string viewName, string targetRegion)
: base(typeof(UserControl))
{
ViewName = viewName;
TargetRegion = targetRegion;
}
public string ViewName { get; private set; }
public string TargetRegion { get; private set; }
}
[Export(typeof(IFluentRegionManager))]
public class FluentRegionManager : IFluentRegionManager, IPartImportsSatisfiedNotification
{
public IRegionManager RegionManager { get; set; }
[ImportingConstructor]
public FluentRegionManager(IRegionManager regionManager)
{
RegionManager = regionManager;
}
/*This Import will find all views in the assembly with attribute [ExportViewToRegion("ViewName", "RegionName")]*/
[ImportMany(AllowRecomposition = true)]
public Lazy<UserControl, IExportViewToRegionMetadata>[] Views { get; set; }
private readonly List<string> _processedViews = new List<string>();
private Lazy<UserControl, IExportViewToRegionMetadata> _GetViewInfo(string viewName)
{
return (from v in Views where v.Metadata.ViewTypeForRegion.Equals(viewName) select v).FirstOrDefault();
}
public IExportViewToRegionMetadata this[string viewName]
{
get
{
return (from v in Views
where v.Metadata.ViewName.Equals(viewName, StringComparison.InvariantCultureIgnoreCase)
select v.Metadata).FirstOrDefault();
}
}
public void ExportViewToRegion(string viewName)
{
if (viewName==null)
{
throw new ArgumentNullException("viewName");
}
var viewInfo = _GetViewInfo(viewName);
string targetRegion;
UserControl _view;
if (viewInfo != null)
{
targetRegion = viewInfo.Metadata.TargetRegion;
_view = viewInfo.Value;
}
if (string.IsNullOrEmpty(targetRegion) || _processedViews.Contains(viewName)) return;
RegionManager.RegisterViewWithRegion(targetRegion, _view.GetType());
_processedViews.Add(viewName);
}
/*All required views has been discovered and imported */
/*Loop true collection and register view with the region */
public void OnImportsSatisfied()
{
foreach (var viewName in from view in Views where !_processedViews.Contains(view.Metadata.ViewName)
select view.Metadata.ViewName)
{
ExportViewToRegion(viewName);
}
}
}
/* finally call IFluentRegionManager import in the bootstrapper to kick off registration*/
I'm trying to create DataGrid in a separate UserControl whose DataContext is a List of T.
In the code behind, I create a List, populate the list, then send it to the constructor for the UserControl on which I have the DataGrid I am trying to populate.
The UserControl class is as follows.
public partial class QuotePreview : UserControl
{
private static SelectionList previewList = new SelectionList();
public SelectionList PreviewList
{
get { return previewList; }
}
public QuotePreview()
{
InitializeComponent();
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
previewList = selectedOptions;
QuotePreviewDataGrid.DataContext = previewList;
}
}
And the Xaml looks like:
<DataGrid Name="QuotePreviewDataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Header="Model Number" Binding="{Binding ModelNumber}"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}"/>
<DataGridTextColumn Header="List Price per Unit" Binding="{Binding Price}"/>
</DataGrid.Columns>
</DataGrid>
I've tried setting the ItemSource as well using
QuotePreviewDataGrid.ItemsSource = PreviewList;
I've also tried setting both the data context and the itemsource as well as refreshing:
QuotePreviewDataGrid.Items.Refresh();
The databinding I have set to listboxes in the rest of my application works perfectly. In the list boxes I have the itemsource set to {Binding} and the ListItems binding set to {Binding Property}. The datacontext for the listboxes set in the code behind.
My datagrid here is setup in the same manner, yet for some reason nothing is being displayed inside the grid.
When I go through the debugger and watch the flow of information, I can see the List of T, SelectionsList being created and passed to the constructor for the user control where the data grid lies. I can see that the DataContext is indeed being set and shows the items in the list, but when I go back to my appication and try to view the data grid, it's blank.
Any help would be greatly appreciated. I've been trying to wrap my mind around this problem for the last day and a half. Thanks!
UPDATE
The SelectionList is setup like:
public class SelectionList : List<Selection>
{
public List<Selection> availableSelections = new List<Selection>();
public List<Selection> AvailableSelections
{
get { return availableSelections; }
}
}
and a Selection is then defined by:
public class Selection : DependencyObject
{
public bool IsChecked { get; set; }
public string ModelNumber { get; set; }
public string Description { get; set; }
public string Price { get; set; }
}
When the application starts, I build a catalog of existing products (Selections). On different tabs, one for each product family, the datacontext for the products list box is initialized with with available products that it grabs from the catalog. Then pending which product a user selects, the available options or child selections associated with that product are populated into the appropriate list boxes, accessories and warranties.
Once a user selects the options they want, a button is clicked to preview the selected items which is supposed to populate the data grid explained above.
I can build the list of selected options, however when I try to set the data context of the data grid, nothing appears. The Lists for available selections are built and set to the appropriate data context the same way I am trying to do it for the data grid, however the data grid doesn't want to display my information.
UPDATE
So after some more debugging, I've narrowed the problem down a bit. The data binding works as it should. I have no real problems there, I don't think. However, the issue I'm running into now is what I believe to be 2 different instances of my User Control, but only the original is being displayed, not the updated copy.
Here's a copy of the class from about with a couple lines I added to help debug the problem.
public partial class QuotePreview : UserControl
{
private SelectionList _selectionList;
private SelectionList temp;
public QuotePreview()
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
}
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
temp = selectedOptions;
_selectionList.AddRange(selectedOptions);
QuotePreview_Loaded();
}
private void QuotePreview_Loaded()
{
foreach (var options in temp.SelectedOptions)
{
_selectionList.SelectedOptions.Add(options);
}
QuotePreviewDataGrid.ItemsSource = _selectionList.SelectedOptions;
}
}
The implementation of the default constructor, is called every time the user control / tab, is clicked on. When that happens, _selectionList is set to the data context of the user control, followed by the Loaded Event which adds a line to my data grid.
In another user control where I select the options I want to add to my data grid user control, I click a button that creates a list of the options I want to be added and calls the custom constructor I wrote. Once the constructor finishes, it calls a custom Loaded Event method that I created for shits and giggles, that adds the selected options to my _selectionList.
Now once I click on the data grid user control again, it goes through the whole default process, and adds another default line.
If I go back a tab and say I want these options again and go back to the data grid, it again goes through the default process and adds another default line.
Whats most intriguing though is that I can see both of the selectionLists build since I dont clear the in between processes. I see a list build of the options i want to display and a list build of the default options build...
Oh, also, SelectionList does implement ObservableCollection.
I finally came up with a solution to the problem.
public static class QuotePreview
{
public static ObservableCollection<PurchasableItem> LineItems { get; private set; }
static QuotePreview()
{
LineItems = new ObservableCollection<PurchasableItem>();
}
public static void Add(List<PurchasableItems> selections)
{
foreach (var selection in selections)
{
LineItems.Add(selection);
}
}
public static void Clear()
{
LineItems.Clear();
}
}
public class QuoteTab : TabItem
{
public ObservableCollection<PurchasableItem> PreviewItems { get; private set; }
public QuoteTab()
{
Initialize()
PreviewItems = QuotePreview.LineItems;
DataGrid.ItemSource = PreviewItems
}
}
Try changing:
QuotePreviewDataGrid.DataContext = previewList;
to
this.DataContext = previewList;
My suspicion is that the ItemsSource="{Binding}" in your xaml is overriding the DataContext code in your constructor.
By changing the previewList to be DataContext of the entire UserControl, then the binding of the DataGrid's ItemsSource can correctly evaluate.
On a side note, I would start looking into the use of ObservableCollection<T> and the MVVM design pattern. An issue you might end up with is that your DataGrid doesn't update when the underlying list changes, using the ObservableCollection<T> will fix this.
Using the MVVM design pattern will give you a good separation of your logic and data (in this case your list and how it's loaded) from the physical display (the DataGrid)