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");
}
}
Related
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.
Here is a very basic question from a WPF novice.
I have a form with some controls such as TextBoxes, DatePickers for example.
In a typical Windows Forms, I would set default values to these in onFormLoad event like
txtName.Text = "N/A";
dpStartDate.Value = DateTime.Now(5); //set 5 days from now
dpEndDate.Value = DateTime.Now(10); //set 10 days from now
How to do this in WPF form and where? This is a very beginner question, where to do it the right WPF-way?
UPDATE:
So far, I have found that I can do this in Window_Loaded event like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Initialize Employee Data
txtName.Text = "N/A";
dpEmplDate.Text = DateTime.Now.AddDays(-100).Date.ToString();
dpTermDate.Text = DateTime.Now.AddDays(1).Date.ToString();
txtAddress.Text = "N/A";
dpDateOfBirth.Text = "";
txtDepartment.Text="N/A";
...
...
}
So, I am setting default employee values like this.
My question is, is this proper WPF way to initialize data?
Thanks,
There is no preferences to set values to Controls till you are not using MVVM pattern.
You can do it in XAML:
<TextBox Name=txtName Text="N/A"/>
or in code-behind:
txtName.Text = "N/A";
But WPF is very cool technology cause it can provide clean separation of concerns between data and view. It can be achieved using MVVM pattern.
It possible to use Binding to send data between data and view and from view to data. So in MVVM pattern data is set from view model. The example of syntax:
View:
<TextBox Text="{Binding FooProperty}"/>>
ViewModel:
public class FooViewModel
{
private string fooProperty="Hello World";
public string FooProperty
{
get { return fooProperty; }
set { fooProperty = value; }
}
}
DataContext property uses to connect View and ViewModel. Data from ViewModel will not be shown without setting DataContext property .
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.
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);
}
I am currently working on a SilverLight 3 Application. I am using MVVM Pattern and Prism. I have everything working except the following item. On one of my views I have to use an OpenFileDialog. I attempted to do this in the ViewModel only to find out the security model of SilverLight prohibits it because it is only allowed to be user initiated. I've since moved the OpenFileDialog code to the code-behind of the View. Here is my problem though. Although I have binding to the source set to TwoWay it is not hitting the setter of the property in my ViewModel.
Example of Image control with binding:
<Image x:Name="imgCard" Height="283" Width="463" Canvas.Left="8" Canvas.Top="8" OpacityMask="White" Source="{Binding Path=CardImage, Mode=TwoWay}"/>
Button Used by user:
<Button x:Name="btnUpload" Height="20" Width="122" Canvas.Left="8" Canvas.Top="319" Content="Upload Image" Click="btnUpload_Click" />
Click Event:
private void btnUpload_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "PNG Files(*.png)|*.png";
ofd.ShowDialog();
using (Stream stream = ofd.File.OpenRead())
{
BitmapImage image = new BitmapImage();
image.SetSource(stream);
imgCard.Source = image;
}
}
My ViewModel is implementing the INotifyPropertyChanged and has the following property.
BitmapSource CardImage
{
get
{
return _imageSource;
}
set
{
_imageSource = value;
NotifyPropertyChanged("CardImage");
}
}
If I put a break point on the Setter. It never hits it.
At least in Silverlight 2, I think the following rule might explain why you are seeing this behavior. "If a Dependency Property is bound and in code the property is set to a value explicitly, the binding is removed." (source)
Maybe this has changed for Silverlight 3? In that case, I have no suggestions.
Ok this is a hack but it works. Because I have to fire the OpenFileDialog from the UI I can instead of updating the control directly reverse tether to the DataContext to update the property. This works and still renders the UI the way I expect.
NOTE: HACK Until I find a better way.
private void btnUpload_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "PNG Files(*.png)|*.png";
ofd.ShowDialog();
using (Stream stream = ofd.File.OpenRead())
{
BitmapImage image = new BitmapImage();
image.SetSource(stream);
BitmapSource b = image;
//HACK: This works but now I'm tethered a bit. This updates the context property CardImage.
((DesignerViewModel) this.DataContext).CardImage = b;
//imgCard.Source = b;
}
}