Loading and binding a serialized view model to a WPF window? - wpf

I'm writing a one-window UI for a simple ETL tool. The UI consists of the window, the code behind for the window, a view model for the window, and the business logic. I wanted to provide functionality to the users to save the state of the UI because the content of about 10-12 text boxes will be reused between sessions, but are specific to the user. I figured I could serialize the view model, which contains all the data from the textboxes, and this works fine, but I'm having trouble loading the information in the serialized XML file back into the text boxes.
Constructor of window:
public ETLWindow()
{
InitializeComponent();
_viewModel = new ViewModel();
this.DataContext = _viewModel;
_viewModel.State = Constants.STATE_IDLE;
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
XAML:
<TextBox x:Name="targetDirectory"
IsReadOnly="true"
Text="{Binding TargetDatabaseDirectory, UpdateSourceTrigger=PropertyChanged}"/>
ViewModel corresponding property:
private string _targetDatabaseDirectory;
[XmlElement()]
public string TargetDatabaseDirectory
{
get { return _targetDatabaseDirectory; }
set { _targetDatabaseDirectory = value; OnPropertyChanged(DataUtilities.General.Utilities.GetPropertyName(() => new ViewModel().TargetDatabaseDirectory)); }
Load event in code behind:
private void loadState_Click(object sender, RoutedEventArgs e)
{
string statePath = this.getFilePath();
_viewModel = ViewModel.LoadModel(statePath);
}
As you can guess, the LoadModel method deserializes the serialized file on the user's drive.
I couldn't find much on the web regarding this issue. I know this probably has something to do with my bindings. Is there some way to refresh on the bindings on the XAML after I deserialize the view model? Or perhaps refresh all properties on the view model? Or am I completely insane thinking any of this could be done?
Thanks.

Assuming that your loadState_Click event is on the Window code behind you could try this.
private void loadState_Click(object sender, RoutedEventArgs e)
{
string statePath = this.getFilePath();
this.DataContext = ViewModel.LoadModel(statePath);
}

Related

Incomplete display of items in combobox wpf c#

I'm using wpf c# and Entity Framework
I have a DataGrid on that show data from database
when users click on datagrid that row will show items in ComboBox (Load on of columns in combobox)
but problem is combobox doesn't show Normal list
Code CS Behind :
DENAF1399Entities dbms = new DENAF1399Entities();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var qre = dbms.Database.SqlQuery<Q_View>("SELECT * FROM Q_View");
datagrid1.ItemsSource = qre.ToList();
}
private void datagrid1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Q_View QVkala = datagrid1.SelectedItem as Q_View;
if (QVkala != null)
{
combobox1.ItemsSource = QVkala.NAMES;
}
}
I tried
-Change Fonts of combobox
-use new combobox
but didn't work
please help me
Edit during formation: It just became obvious to me what's going on. Q_View.NAMES is a string, and by setting combobox1.ItemsSource to that property, it's identifying the individual items as characters in the string (as string is an IEnumerable<char>).
If what you want in the combo box is what's in each of the columns of the selected item, then the way to do that is like this:
private void datagrid1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Q_View QVkala = datagrid1.SelectedItem as Q_View;
if (QVkala != null)
{
object[] items = { QVkala.CODE, QVkala.NAME, QVkala.NAMES, QVkala.TOZIH } //etc whatever properties you want to project into this
combobox1.ItemsSource = items;
}
}
ORIGINAL WORK ON AN ANSWER
At first glance it looks like your data is transposed, but altogether it looks like you aren't using WPF or Entity Framework like you really could be using them. WPF was made for MVVM design and Entity Framework was made for treating tables like collections of objects. Not knowing much else about your application, here's how I'd get started:
First, I'd move basically everything except what's auto-generated out of MainWindow.xaml.cs, and start a new separate class. (Note: may have compiler errors as this is completely off the cuff)
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged; //MainWindow.xaml will hook into this
public ObservableCollection<Q_View> Q_Views { get; private set; }
private Q_View selectedQView;
public Q_View SelectedQView
{
get => selectedQView;
set
{
if(value != selectedQView)
{
selectedQView = value;
PropertyChanged?.Invoke("SelectedQView");
}
}
}
}
And then in MainWindow.xaml.cs, the only change from what's generated would be the constructor (there's another way to do this even without changing the code-behind but I'll not get into it here since I'm not as xaml-adept as I am with C#)
public class MainWindow : Window
{
public MainWindow()
{
DataContext = new MainWindowViewModel();
InitializeComponent(); //that's auto-generated
}
}
And finally, the xaml for your DataGrid. Edit it like this:
<DataGrid Name="QViewDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Q_Views}" SelectedItem="{Binding SelectedQView}">
<DataGrid.Columns>
<DataGridTextColumn Header="CODE" Binding="{Binding Path="CODE"}"> //and so forth with more columns
</DataGrid.Columns>
</DataGrid>
ComboBox will have a similar syntax for binding an ItemsSource and SelectedItem. Doing this enables you to avoid having event handlers and dealing with boiler plate for updating so many things.

WPF MVVM share object between view model and code behind

I am trying to share the following object between my view model and code behind
Dictionary<ItemTypeType, Dictionary<string, Dictionary<int,List<ConfigParameter>>>> ItemToConfigParametersValues
This object is not used in the XAML. It is used in the code behind for several dynamically generated UI Elements.
What is the MVVM Light way of doing this?
Well, the ViewModel is typically set as the "DataContext" of the View. Code-behind is part of the View.
So... just expose the data from your ViewModel. In your code-behind, you can access it using the DataContext property (with appropriate casting).
I would store the viewmodel in a variable so I didnt need to cast DataContext everytime... In the MainWindow.xaml.cs code behind for example:
private MainWindowViewModel _vm;
public MainWindow()
{
InitializeComponent();
this._vm = new MainWindowViewModel();
// this._vm.MyProperty = ... (or does the vm instantiate MyProperty?)
this.DataContext = this._vm;
}
private void HandleSomeEvent(object sender, RoutedEventArgs e)
{
var sharedObject = _vm.MyProperty;
}

Custom Usercontrol with MVVM and Catel

I've created a custom usercontrol that's composed of a AutoCompleteBox with a Selected Item... till now I've implemented it in a way I don't like... I mean I've a XAML view, a Viewmodel and in the viewmodel I load data from a stored procedure.
Since the AutoComplete box is a third party UserControl I've added it to the XAML view and not defined as a custom usercontrol. What's the best practice to do so?
I think the fact that I'm using Catel as MVVM Framework is irrilevant right now..
Thanks
UPDATE #1
My usercontrols need to have some properties that are passed via XAML for example (LoadDefaultValue)
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding Model.PortfolioModel}" Height="25" LoadDefaultValue="True" Width="150" />
To achieve such a scenario I had to define a dependency property in my PortfolioChooserView defined as
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
Since if I would have defined it in Viewmodel only I wouldn't have been able to set it.
The odd thing is that in order to pass it to the viewmodel I had to do such a trick
public PortfolioChooserView()
{
InitializeComponent();
if (!isFirstLoad) return;
Focusable = true;
PortfolioCompleteBox.AllowDrop = true;
PortfolioCompleteBox.Focus();
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
DataContextChanged += PortfolioChooserView_DataContextChanged;
isFirstLoad = false;
}
void PortfolioChooserView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataContext = DataContext as PortfolioModel;
if (dataContext != null)
{
dataContext.LoadDefaultValue = LoadDefaultValue;
dataContext.AllowNull = AllowNull;
//var converter = new PortfolioConverter();
//var portfolio = (Portfolio) converter.Convert(SelectedItem, null, null, CultureInfo.CurrentCulture);
//dataContext.SelectedItem = portfolio;
}
}
But I really dislike to use the DataContextChanged event ...do you see a better approach?
Thank
UPDATE#2
I keep this toghether since It's a related question...
On some viewmodel I used DeferValidationUntilFirstSaveCall = true; in the Constructor to disable the validation at load but my custom usercontrols shows the red border around... what should I do to propagate that info to the nested usercontrols?
Thanks again
See Orc.Controls for tons of examples. It's an open-source library that has a lot of user controls built with Catel, even one with an auto complete box.

Data binding across several windows with WPF / Entity Framework? Pass context around or not?

Most WPF/EF tutorials only cover databinding in one window. However, in reality data gets displayed across many windows. You often display a record in the first window and dig deeper in related details in the next windows.
So, this also is the case in my scenario. Here you can see my data structure and the ui. Actually I am not dealing with Customers and Invoices, but the structure is the same. (My concrete questions are at the very end.)
In the InvoicesWindow I can select an Invoice and press "Show Invoice". That opens a CustomerWindow displaying Customer details and his invoices. The right invoice is pre-selected. To each Invoice displayed in the CustomerWindow I can add Items or edit them. This is done in a seperated window called "ItemWindow". Editing the DataGrids is not an option. They are set to ReadOnly.
Here is the code of the wpf-window classes (I only have done displaying data yet, not saving):
Invoices Window:
public partial class InvoicesWindow : Window
{
private MyEntities context = new MyEntities();
public InvoicesWindow ()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
CollectionViewSource invoicesViewSource = (CollectionViewSource)FindResource("invoicesViewSource");
invoicesViewSource.Source = context.Invoices;
}
private void ShowInvoice_Click(object sender, RoutedEventArgs e)
{
Invoice selectedInvoice = (Invoice)InvoicesDataGrid.SelectedItem;
var customerWindow = new CustomerWindow(selectedInvoice);
customerWindow.ShowDialog();
}
}
Customer Window:
public partial class CustomerWindow : Window
{
private MyEntities context = new MyEntities();
private Invoice selectedInvoice;
public CustomerWindow()
{
InitializeComponent();
}
public CustomerWindow (Invoice selectedInvoice)
{
InitializeComponent();
this.selectedInvoice = selectedInvoice;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Set the data
CollectionViewSource customerViewSource = (CollectionViewSource)FindResource("customerViewSource ");
customerViewSource.Source = context.Customers.Where(p => p.id == selectedInvoice.Customer.id);
//Select the right invoice
CollectionViewSource customerInvoicesViewSource = (CollectionViewSource)FindResource("customerInvoicesViewSource ");
customerInvoicesViewSource.Items.MoveCurrentTo(((ObjectSet<Invoice>)customerInvoicesViewSource.Source).Where(p => p.id == selectedInvoice.id).SingleOrDefault());
}
private void EditItem_Click(object sender, RoutedEventArgs e)
{
Item selectedItem = (Item)ItemsDataGrid.SelectedItem;
var itemWindow = new ItemWindow((IQueryable<Customer>)(customerViewSource.Source),selectedInvoice,selectedItem);
itemWindow.ShowDialog();
}
}
Item window:
public partial class ItemWindow : Window
{
private Invoice _selectedInvoice;
private Invoice _selectedItem;
private IQueryable<Customer> _customers;
public ItemWindo()
{
InitializeComponent();
}
public ItemWindow(IQueryable<Customer> customers, Invoice selectedInvoice, Item selectedItem)
{
InitializeComponent();
this._customers = customers;
this._selectedInvoice = selectedInvoice;
this._selectedItem = selectedItem;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Set the data
CollectionViewSource customerViewSource = (CollectionViewSource)FindResource("customerViewSource");
invoicesViewSource.Source = _customers;
//Select the right invoice
CollectionViewSource customerInvoicesViewSource = (CollectionViewSource)FindResource("customerInvoicesViewSource ");
customerInvoicesViewSource.Items.MoveCurrentTo(_selectedInvoice);
//Select the right item
CollectionViewSource customerInvoicesItemsViewSource = (CollectionViewSource)FindResource("customerInvoicesItems");
customerInvoicesItems.Items.MoveCurrentTo(_selectedItem);
}
}
I wrote the code out of my mind. So, maybe some casts are missing and some methods are mis-spelled. I hope I got the right type with "ObjectSet", it also could be "ObjectCollection" or something like that.
The XAML was created widely with assistance of VS2010 like in this video: http://msdn.microsoft.com/de-de/data/ff806174.aspx
So, finally my questions ;)
Is the design of binding I applied correct?
In CustomerWindow I create a new context.
Between CustomerWindow and ItemWindow I just pass the data of the same context and select the current item manually.
In CustomerWindow I use a ObjectSet (or ObjectCollection, I'm not sure about the type anymore) with a single entry as Source for the customersCollectionViewSource. This works fine. However, there is no need for a collection, because I only edit a single Customer. I did not manage to set a single Customer as Source. I didn't know how to adjust the view source which was generated by VS2010.
I haven't done saving yet. But I think I am going to run into problems due to my design between CustomerWindow and ItemWindow. Maybe you can give me some advice here.
When the "Apply"-Button in ItemWindow gets pressed, the Item data should be updated in DB. But not the Customer- and Invoices-related data in the CustomerWindow underneath.
The DataGrid of Items in CustomerWindow should get updated, when closing the ItemWindow. But not the rest of the fields in the CustomerWindow, since here could have been data changed before opening the ItemWindow.
The only solution for me to overcome that "synchronisation problem": The User is forced to press "Apply" in the CustomerWindow before he can press the "New Item" or "Edit Item", if there have been any changes. (Kinda like the "window resolution control" of windows 7 when working with two monitors) But this is not too user friendly.
A cleaner design would be to use the MVVM design pattern.
Inject the view model into the window's context and bind the view model to either a collection of entities or a single entity, bind in the xaml to properties in the view model(s) and use commands implemented in the view model for actions e.g. add new, delete.
The windows shouldn't be aware of the context.
If you have a list view model + window and a details window (preferably with a view model), then the list view model should pass the selected item to the details view model (or window) as the context.
If the windows are not open at the same time or do not have related objects, then their views models should not share a database context, otherwise, in order for the changes to be reflected easily between the windows, they will have to share the database context.

WPF & MVVM : Update an image field without breaking the pattern

I'm currently learning how to write a WPF application using the MVVM pattern. I'm writing a little contact manager application, so my app displays a Listbox bound to my View Model, and a set of fields bound to ListBox.SelectedItem. One of these fields is the contact's photo.
I'd like to change the photo in the edit part using OpenFileDialog, so the Listbox item would be updated, as it is for all of the other fields.
I first tried to update the source property of the Image control, but doing this, I lose the Binding...
Then I wrote an handler on Button_Click to update the Contact.Photo property (its type is byte[]), and it works. But instead of binding from the "update control" to the view model, binding is from the VM to the control, as if the data came from the DB.
(In the code, LoadPhoto returns a byte[])
private void Button_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog OpenFileDialog = new OpenFileDialog();
if (OpenFileDialog.ShowDialog() == true)
{
(listbox.SelectedItem as ContactManager.ViewModel.Contact).Photo =
LoadPhoto(OpenFileDialog.FileName);
}
}
I wonder if it doesn't break the MVVM pattern... I'm not sure of what could be made in the View... Is it the right way to update the Contact object ? Does anyone have a better solution to this problem ?
Look into binding your button to a Command Binding instead of the click event.
You can find implementations of DelegateCommand using Google.
Next you can expose a ImageSource from your ViewModel that you can bind to your Image from your XAML.
I've included some code fragments to get you started.
Once you get past the basics take a look at MVVM Frameworks, like Cinch, you'll find a way to handle OpenFileDialog using the Services Interfaces IOpenFileService.cs to not violate the MVVM pattern.
Here is the XAML:
<Button Content="Update Photo" Command="{Binding UpdatePictureCommand}"/>
<Image Source="{Binding EmployeePicture}"
VerticalAlignment="Center" HorizontalAlignment="Center"
Stretch="Fill" />
Here is the ViewModel:
public MainViewModel()
{
UpdatePictureCommand = new DelegateCommand<object>(OnUpdatePictureCommand, CanUpdatePictureCommand);
}
public ICommand UpdatePictureCommand { get; private set; }
private void OnUpdatePictureCommand(object obj)
{
OpenFileDialog OpenFileDialog = new OpenFileDialog();
if (OpenFileDialog.ShowDialog() == true)
{
//(listbox.SelectedItem as ContactManager.ViewModel.Contact).Photo =
// LoadPhoto(OpenFileDialog.FileName);
Stream reader = File.OpenRead(OpenFileDialog.FileName);
System.Drawing.Image photo = System.Drawing.Image.FromStream((Stream)reader);
MemoryStream finalStream = new MemoryStream();
photo.Save(finalStream, ImageFormat.Png);
// translate to image source
PngBitmapDecoder decoder = new PngBitmapDecoder(finalStream, BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.Default);
EmployeePicture = decoder.Frames[0];;
}
private bool CanMoveFirstCommand(object obj)
{
return true;
}
private ImageSource _employeePicture;
public ImageSource EmployeePicture
{
get
{
return _employeePicture;
}
set
{
_employeePicture = value;
OnPropertyChanged("EmployeePicture");
}
}

Resources