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}"/>
Related
I'm trying to have an MVVM architecture while Models are EF Models too.
In Code :
Model:
public class NotaireDBContext : DbContext
{
public DbSet<Paquet> Paquets { get; set; }
public DbSet<Personne> Personnes { get; set; }
public DbSet<Contrat> Contrats { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite(#"Data Source=db/Notaire.db");
}
public class Paquet
{
public int PaquetId { get; set; }
public string Numero { get; set; }
public DateTime Date { get; set; }
public string Volume { get; set; }
public string Page { get; set; }
public string Etat { get; set; }
public List<Contrat> Contrats { get; } = new List<Contrat>();
}
public class Personne
{
public int PersonneId { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Nom_pere { get; set; }
public PieceIdentite Piece_identite { get; set; }
public string Num_piece { get; set; }
public string Lieu_naissance { get; set; }
public string Date_naissance { get; set; }
public string Commune { get; set; }
public string Numero_acte { get; set; }
public string Laiv_carte { get; set; } //??????????????
public string Adresse { get; set; }
public string Nationalite { get; set; }
public string Fonction { get; set; }
}
public class Contrat
{
public int ContratId { get; set; }
public string Numero { get; set; }
public DateTime Date { get; set; }
public List<Personne> Partie_1 { get; set; }
public List<Personne> Partie_2 { get; set; }
public int PaquetId { get; set; }
public Paquet Paquet { get; set; }
}
Views :
PaquetsView.xaml (this is a view of all paquets)
<ScrollViewer Background="#EBEEF5" HorizontalScrollBarVisibility="Disabled"
FlowDirection="RightToLeft">
<ItemsControl x:Name="PaquetsControl" Padding="4">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="5"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:PaquetControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!--<controls:PaquetControl/>
<controls:PaquetControl/>-->
</ItemsControl>
</ScrollViewer>
I bind it's ItemsSource like this in PaquetsView.xaml.cs :
public partial class PaquetsView : UserControl
{
private NotaireDBContext db = new NotaireDBContext();
public PaquetsView()
{
InitializeComponent();
PaquetsControl.ItemsSource = (from p in db.Paquets select p).ToList();
}
}
The DataTemplate of PaquetView.xaml -> ItemsControl stand in another xaml file (PaquetControl.xaml), which is a UserControl consisting of TextBlocks and Buttons with Menu (and menu item), that show the data held in on Paquet, and should be able to edit/delete said Paquet.
A portion of it :
...
<Button x:Name="MoreButton" Style="{DynamicResource MoreButtonTemplate}"
Grid.Column="2" Click="MoreButtonClicked" Margin="0,-4,-4,0">
<Button.ContextMenu>
<ContextMenu Background="White" FlowDirection="RightToLeft">
<MenuItem Header="Edit" Click="EditMenuItemClick"/>
<MenuItem Header="Archive" Click="ArchiveMenuItemClick"/>
<MenuItem Header="حذف" Click="DeleteMenuItemClick"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
...
<TextBlock Grid.Column="0" Text="{Binding Path=Numero}" FontSize="22" Foreground="Black"/>
...
<TextBlock Grid.Row="1" Text="{Binding Path=Date, StringFormat=yyyy/MM/dd}"
Foreground="Black" FontSize="16"/>
...
<!--other TextBlock binded-->
Now I would like to know how can I make it CRUD with updates of the view.
Summarize, I have an SQLite DB (code first) for data persistence, I can get that data with DBContext, but now I'm seeing that it's better to use MVVM rather than create DBContext each time.
This is a very large topic and I doubt that it will fit the format adopted here.
Therefore, I will briefly outline only the main points.
It is customary to implement WPF using the MVVM pattern.
It is a strict 3-tier architecture: View (WPF) -> ViewModel -> Model.
The Model is responsible for working with "real" data - this is the so-called Business Logic.
View is responsible for creating the GUI.
The peculiarity of WPF is that the UI elements themselves request the data they need through the bindings.
Bindings are created (mostly) to the DataContext.
Therefore, it is necessary to put some special custom type there, which is responsible for the links between the View and the Model.
This type is called ViewModel.
In a typical implementation, the Model basically receives / returns data via methods.
And bindings need properties.
Therefore, one of the main functions of the ViewModel is to provide all the data necessary for the View in its properties.
When an application works with a database, it is customary in Sharpe to implement this in the Repository (Data) pattern.
From the MVVM point of view, such a Repository is part of the Model.
But, for a simpler understanding, to facilitate software maintenance, the Repository is usually implemented in a separate layer.
As a result, we get a four-tier architecture: View -> ViewModel -> Model -> Repository.
According to the rules and principles of OOP, SOLID in a layered architecture, each layer "knows" (has information) only about the underlying layer.
And all non-public information must be encapsulated inside the layer.
EF entities reflect database data, they are mutable, and can have corresponding attributes.
When changing the source, these types can change.
Let's say you, at some point, want to use a set of XML files instead of a database.
And they need entities of a different type.
Therefore, such entities are the internal implementation of the Repository.
And to exchange data with the Model, the Repository must be either Model types or generic DTO types.
At the next level, the ViewModel must also receive data from the Model.
But Model types cannot be used here, since they can be implicitly associated with Business Logic and there is a possibility of creating parasitic connections leading to unpredictable bugs.
At this level (ViewMode-> Model), exclusively DTO types are used for data exchange.
They are preferably immutable.
The next level of exchange of View with ViewModel.
First, the GUI often needs mutable properties. And to auto-update the property view, the type MUST implement INotifyPropertyChanged.
Secondly, to call actions from the GUI, the ViewModel must provide COMMANDS in its properties - this is the ICommand implementation.
For my answers here, I am using the BaseInpc and RelayCommand classes.
Thirdly, in types for View, additional properties are often needed to ensure the logic of the GUI operation: a selected element, an expanded element, instead of the Id of the record, an element reflecting it, etc.
For these reasons, at the ViewModel level, you already need your own types with an implementation other than DTO.
As a result of the above, we can get four different implementations for the type reflecting some record in the database.
The area of use of each type will be in one or two layers.
In order not to get confused in all this, it is better to do each layer in a separate project.
Types used only within the layer are implemented in this project.
The types used by several layers (for example, DTO) are implemented in separate libraries (maybe for simple tasks and one common library).
To maintain abstraction, it is desirable to do all implementations through the preliminary declaration of interfaces.
And transfer information between layers through these interfaces.
Dear Programmers,
I have a question that I can't succeed to figure out, it is more related to how should I design the application, and until now I have always overcome this issue by writing a lot of code.
I have to design a silverlight application connected to a database, and I want to use a WCF service between these 2 for many reasons.
If I use the WCF service I will loose the dbcontext structure which is very great when we want to use CollectionViewSource with related fields to bind the xaml controls.
By example, lets say we have simple entities like that, generated from the edmx file: Resistor and ResistorCategories
public partial class ResistorCategories
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public ResistorCategories()
{
this.Name = "New";
this.ResistorsSet = new HashSet<ResistorsSet>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ResistorsSet> ResistorsSet { get; set; }
}
public partial class ResistorsSet
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public ResistorsSet()
{
this.ResistorStockEntriesSet = new HashSet<ResistorStockEntriesSet>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Resistance { get; set; }
public Nullable<decimal> PowerRating { get; set; }
public Nullable<decimal> Price { get; set; }
public Nullable<decimal> Tolerance { get; set; }
public string SupplierCode { get; set; }
public string ManufacturerCode { get; set; }
public int ResistorCategories_Id { get; set; }
public Nullable<int> Suppliers_Id { get; set; }
public Nullable<int> Manufacturers_Id { get; set; }
public virtual ResistorCategories ResistorCategories { get; set; }
public virtual Suppliers Suppliers { get; set; }
public virtual Manufacturers Manufacturers { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<ResistorStockEntriesSet> ResistorStockEntriesSet { get; set; }
}
In the xaml, it is very easy to place 2 datagrid and see the list of resistors which belong to a categorie:
In the xaml:
Then I bind each datagrid to its corresponding CollectionViewSource:
ItemsSource="{Binding Source={StaticResource resistorCategoriesViewSource}, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Source={StaticResource resistorCategoriesResistorsViewSource}, UpdateSourceTrigger=PropertyChanged}"
And in the code behind file, with lazyloading enabled:
System.Windows.Data.CollectionViewSource resistorCategoriesViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("resistorCategoriesViewSource")));
//
_context.ResistorCategoriesSet.Load();
resistorCategoriesViewSource.Source = _context.ResistorCategoriesSet.Local;
So it can't be easier to display records with their related fields, but in this case the xaml client is querying the database directly and this is what I want to avoid with a WCF service, + adding some standardization and security.
With a WCF, first I have to generate the POCO objects with EF Power Tool, because it can't return IEnumerable by example because of its virtual property (even if I deactivate lazyloading and proxy).
Then I have to query and store in separated collections the POCO objects:
ObservableCollection && ObservableCollection
Then I bind each datagrid to its collection and monitor the selecteditem event to manually change the collection displayed in the datagrid for the resistors!
So I guess there is a solution to use a WCF together with the simplicity of the dbcontext!
Please help me! Thanks in advance
May be, you can implement your own "data provider", based on sample data provider: Entity Framework 5 Sample Provider. But you need to "tunnel" and serialize data to send/receive throught wcf.
Thx for your message, I think yes finally I will have to do sth like that, I am struggling trying to find an easy solution but I guess what I want to do is not possible.
Instead of trying to save time with the binding I should focus on saving time with the generation of data services. wcf data service is a good solution, I also tried codetrigger and I am actually trying to implement it with open ria services..... which seems to be buggy as hell!!
So I don't know honestly, I can do the data part using wcf data services, but I would like the silverlight app to retrieve also the name and roles on the currently authenticated user in the asp.net app in order to customized the UI. So I guess I have to use a 2nd wcf service just for that.
I am maybe dreaming but if possible I would like the silverlight app to receive a notification when a user logged in or out instead of querying the asp.net app all the times.
If I find a solution of course I will post it, because I think it is quite common when someone want to write a silverlight app.
So I have found a solution finally, it was worth searching even if the solution is easy: using WCF Data Services.
It's possible to query the database with all related entities and bind them in the xaml:
As example, consider an entity called ItemsCategory with a list of Items referring to it via a ItemCategory_Id field, and 2 others entities called ItemPrices and ItemImages referring Items via an Item_Id field:
ItemsCategory
Item
ItemPrices
ItemImages
It's not perfect but it looks like that.
So then if you want to place a datagrid for the ItemsCategory and see the related Items in a second datagrid, and the related ItemPrices and ItemImages in a third and fourth datagrid in your xaml you defined it like that:
<navigation:Page.Resources>
<CollectionViewSource x:Key="itemsCategoriesSetViewSource" d:DesignSource="{d:DesignInstance SvcCatalogDatabase:ItemsCategories, CreateList=True}"/>
<CollectionViewSource x:Key="itemsCategoriesSetItemsSetViewSource" Source="{Binding Items, Source={StaticResource itemsCategoriesSetViewSource}}"/>
<CollectionViewSource x:Key="itemsCategoriesSetItemsSetItemsPricesSetViewSource" Source="{Binding ItemsPrices, Source={StaticResource itemsCategoriesSetItemsSetViewSource}}"/>
<CollectionViewSource x:Key="itemsCategoriesSetItemsSetItemsImagesSetViewSource" Source="{Binding ItemsImages, Source={StaticResource itemsCategoriesSetItemsSetViewSource}}"/>
</navigation:Page.Resources>
To load the entities you first define a collections like that:
public DataServiceCollection<ItemsCategories> ItemsCategoriesTracked { get; set; }
public CollectionViewSource ItemsCategories_CVSrc { get; set; }
And to query the database you just need to query ItemCategories with related entities:
ItemsCategories_CVSrc.Source = null;
ItemsCategoriesTracked.LoadAsync(this.SvcData.ItemsCategoriesSet.Expand("Items").Expand("Items/ItemsPrices,Items/ItemsImages"));
Of course you have defined a handler for the LoadCompleted event of the DataServiceCollection:
ItemsCategoriesTracked.LoadCompleted += new EventHandler<LoadCompletedEventArgs>((sender, e) => DataServiceCollection_LoadCompleted<ItemsCategories>(sender, e, ItemsCategories_CVSrc));
private void DataServiceCollection_LoadCompleted<T>(object sender, LoadCompletedEventArgs e, CollectionViewSource target)
{
if (e.Error == null)
{
if ((sender as DataServiceCollection<T>).Continuation != null)
{
(sender as DataServiceCollection<T>).LoadNextPartialSetAsync();
}
else
{
target.Source = (sender as DataServiceCollection<T>);
}
}
else
{
MessageBox.Show(string.Format("{0}: An error has occured: {1}", typeof(T).Name, e.Error.Message));
}
}
And you've linked the CollectionViewSource to the one defined in the XAML:
ItemsCategories_CVSrc = (CollectionViewSource)this.Resources["itemsCategoriesSetViewSource"];
Voilà! Hope it helps someone!
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.
I have a list on my XAML page bind to my ViewModel. The list Show only the entries - there is no Feature to edit or update them (they are read from Server api).
In the application bar I have a button for reloading the list (sending again the request to the Server).
What must I do for this "reload Feature"?
I think about following:
removing the existing collection of my entries
firering the LoadData again
Are there any snippets for my question?
What is about Memory issues because of my previous existing collection?
Something like this would work if you think your callback will be pretty light. If you think it may be heavy with a lot of items coming back then this may not be the most efficient way but would still work:
public class YourViewModel
{
public ObservableCollection<YourDataType> YourCollection { get; set; }
public ICommand ReloadDataCommand { get; set; }
public YourViewModel()
{
YourCollection = new ObservableCollection<YourDataType>();
ReloadDataCommand = new DelegateCommand(ReloadData);
}
private void ReloadData()
{
//Get your new data;
YourCollection = new ObservableCollection(someService.GetData());
RaisePropertyChange("YourCollection");
//Depending on how many items your bringing in will depend on whether its a good idea to recreate the whole collection like this. If its too big then you may be better off removing/adding these items as needed.
}
}
In XAML:
<Button Content="Reload" Command="{Binding ReloadDataCommand}" />
<List ItemsSource="{Binding YourCollection}">
<!-- All your other list stuff -->
</List>
Hope this helps
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.