I have a Silverlight project I'm converting to MVVM. It's my first time using the pattern and I'm struggling with something.
So basically I had this in the XAML code behind page:
OpenFileDialog ofd = new OpenFileDialog();
if ((bool)ofd.ShowDialog())
{
_fileName = ofd.File.Name;
FileStream fs = ofd.File.OpenRead();
fileSize = (double)fs.Length;
txtFileName.Text = fileName;
index = 0;
sendData = 0;
byte[] file = new byte[fs.Length];
fs.Read(file, 0, file.Length);
//convertToChunks(file);
prgUpload.Maximum = fileChunks.Count;
prgUpload.Value = 0;
//uploadChunks(index);
}
And I cannot figure out how to wire it up to be able to use that in the model? I assume the viewmodel comes into play, but nothing is working.
Any thoughts?
Here is the work in progress XAML:
<Grid x:Name="LayoutRoot" Width="475" Height="340">
<Canvas Margin="8,8,0,0" Background="White" Height="320" VerticalAlignment="Top" HorizontalAlignment="Left" Width="475">
<Button Width="75" Canvas.Left="380" Canvas.Top="43" Content="Browse" x:Name="btnBrowse" />
<TextBox Canvas.Left="25" IsReadOnly="True" Canvas.Top="43" TextWrapping="Wrap" Width="350" Text="{Binding Path=FileUploadName}" x:Name="txtFileName" />
<ProgressBar Height="10" Width="350" Canvas.Left="25" Canvas.Top="99" x:Name="prgUpload" />
<my:Label Content="Please select a file to upload" Name="lblError" Canvas.Left="25" Canvas.Top="23" RenderTransformOrigin="0.133,-0.063" Width="220"/>
<my:Label x:Name="lblProgress" Canvas.Left="25" Canvas.Top="78" RenderTransformOrigin="0.133,-0.063" Width="220"/>
</Canvas>
</Grid>
Basically I want it to fire after the user selects a file to upload.
If you want to fire a command this would do the work for you
<Button Width="75" Canvas.Left="380" Canvas.Top="43" Content="Browse" x:Name="btnBrowse"
Command={Binding OpenFileCommand} />
in your code behind Constructor for example
partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext=new MainViewModel();
}
}
and in your ViewModel
public ICommand OpenFileCommand { get; set; }
public MainViewModel()
{
OpenFileCommand = new RelayCommand(OpenDialog) { IsEnabled = true };
}
private void OpenDialog()
{
OpenFileDialog ofd = new OpenFileDialog();
if ((bool)ofd.ShowDialog())
{
_fileName = ofd.File.Name;
FileStream fs = ofd.File.OpenRead();
fileSize = (double)fs.Length;
//txtFileName.Text = fileName;// Apply Binding
index = 0;
sendData = 0;
byte[] file = new byte[fs.Length];
fs.Read(file, 0, file.Length);
//convertToChunks(file);
prgUpload.Maximum = fileChunks.Count;
prgUpload.Value = 0;
//uploadChunks(index);
}
}
And the RelayCommand
public class RelayCommand:ICommand
{
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
private Action _handler;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
in order to get the filename in your textbox you have to bind the textbox to to the view model. so that it will appear on the UI and also implement INotifyPropertyChanged. Also Look at this it would be helpful Silverlight MVVM
Related
I have a WPF of tasks each has status of completed or pending, I will view all of these items into a list box, the tasks are serialized into XML file and these items are linked to UI through ObersvableCollection.
What I'm looking for it so be able to filter the tasks on the view and be able to edit or create new taks and save into the XML file.
I was thinking about creating a new class called FilterTask.cs and have static method Completed() which return ObersvableCollection item but I am not sure if this will affect the serialization process.
here is my code
main window XAML
<Window x:Class="UIToDoList_2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UIToDoList_2"
mc:Ignorable="d"
Title="To Do List" Height="290" Width="500" ResizeMode="CanMinimize">
<Grid Margin="0,0,0,0">
<StackPanel Name="MainPanel">
<StackPanel Name="ViewTaskPanel" Margin="10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Task" Margin="0,0,10,0"/>
<StackPanel Orientation="Vertical">
<ListBox x:Name="UITasks" Width="300" Height="100" VerticalAlignment="Top" Margin="0,0,10,5"
DisplayMemberPath="Name"/>
<RadioButton x:Name="ShowAll" Content="Show All"/>
<RadioButton x:Name="ShowCompleted" Content="Show Completed" />
<RadioButton x:Name="ShowPending" Content="Show Pending"/>
</StackPanel>
<StackPanel>
<Button Content="Mark As Completed" Margin="0,0,0,5"/>
<Button Content="Mark As Pending" Margin="0,0,0,5"/>
<Button x:Name="DltTaskBtn" Content="Delete Task"
Margin="0,0,0,5" Click="DltTaskBtn_Click"/>
<Button x:Name="RenameBtn" Content="Rename" Click="RenameBtn_Click"/>
</StackPanel>
</StackPanel>
</StackPanel>
<StackPanel Name="AddTaskPanel" Margin="10">
<TextBlock Text="Add New Task"/>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="Task Name" Margin="0,0,10,0"/>
<TextBox Name="TaskNameTxt" Width="260"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button x:Name="AddTaskBtn" Content="Add" Margin="5" Click="AddTaskBtn_Click"/>
<Button x:Name="ClearTaskTxt" Content="Clear" Margin="5" Click="ClearTaskTxt_Click"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
</Window>
Task.cs
public class Task : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return this._Name; }
set
{
if (this._Name != value)
{
this._Name = value;
this.NotifyPropertyChanged();
}
}
}
private Status _Status;
public Status Status
{
get { return this._Status; }
set
{
if (this._Status != value)
{
this._Status = value;
this.NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
public enum Status
{
Pending = 0,
Completed = 1
}
TaskSerialization.cs
public class TaskSerialization
{
public string FileName { get; set; }
public TaskSerialization(string fileName)
{
FileName = fileName;
}
/// <summary>
/// Serialize the task list to XML File
/// </summary>
/// <param name="tasks"></param>
public void SerializeTasks(List<Task> tasks)
{
StreamWriter sw = new StreamWriter(FileName);
XmlSerializer sr = new XmlSerializer(typeof(List<Task>));
sr.Serialize(sw, tasks);
sw.Close();
}
/// <summary>
/// Deserialize the taks list form XML file and return as ObservableCollection item
/// </summary>
/// <returns></returns>
public ObservableCollection<Task> DeserializeTasks()
{
XmlSerializer sr = new XmlSerializer(typeof(List<Task>));
FileStream fs = new FileStream(FileName, FileMode.Open);
return new ObservableCollection<Task>((List<Task>)sr.Deserialize(fs));
}
}
Mainwindow.cs
namespace UIToDoList_2
{
public partial class MainWindow : Window
{
private readonly string fileName = "ToDoList.xml";
private readonly TaskSerialization taskSrlz;
ObservableCollection<Task> tasks = new ObservableCollection<Task>();
public MainWindow()
{
InitializeComponent();
taskSrlz = new TaskSerialization(fileName);
tasks = taskSrlz.DeserializeTasks();
UITasks.ItemsSource = tasks;
}
private void AddTaskBtn_Click(object sender, RoutedEventArgs e)
{
AddTask();
}
private void DltTaskBtn_Click(object sender, RoutedEventArgs e)
{
DeleteTask();
}
private void AddTask()
{
var taskName = TaskNameTxt.Text;
if (!string.IsNullOrWhiteSpace(taskName))
{
tasks.Add(new Task() { Name = TaskNameTxt.Text, Status = Status.Pending });
taskSrlz.SerializeTasks(new List<Task>(tasks));
}
else
MessageBox.Show("Task name cannot be null", "Error in Task name", MessageBoxButton.OK, MessageBoxImage.Error);
}
private void DeleteTask()
{
if(UITasks.SelectedItem != null)
{
var selectedTask = (UITasks.SelectedItem as Task);
tasks.Remove(selectedTask);
taskSrlz.SerializeTasks(new List<Task>(tasks));
}
}
private void RenameBtn_Click(object sender, RoutedEventArgs e)
{
if (UITasks.SelectedItem != null)
{
(UITasks.SelectedItem as Task).Name = TaskNameTxt.Text;
taskSrlz.SerializeTasks(new List<Task>(tasks));
}
}
private void ClearTaskTxt_Click(object sender, RoutedEventArgs e)
{
TaskNameTxt.Clear();
}
}
}
I will mark this as answer based on clemens comment to look into binding into collections https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-binding-overview#binding-to-collections
Explanation:
I have to get FolderBrowserDialog Box once I click the browse button. And In FolderBrowserDialog Box,if I select any folder and click ok,that particular folderpath along with foldername should be shown in the textbox which is beside the browse button....But I didn't get anything once I click Browse button.
Please check my code and correct me...
View.xaml :
<Window.... xmlns:VM="clr-namespace:myproject.myViewModel"
... >
<Window.DataContext><VM:myViewModel/>
<Grid>...
<TextBlock Text="Folder to save files" VerticalAlignment="Center" />
<TextBox Text="{Binding Path=FoldernameWithPath , UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Height="26" IsReadOnly="True" VerticalContentAlignment="Center" Width="150" />
<Button Content="Browse" Height="26" VerticalAlignment="Bottom" MinWidth="45" Command="{Binding OpenFolderCommand}" />
</Grid>
</window>
ViewModel.cs
public ICommand OpenFolderCommand
{
get => new RelayCommand(a => this.OpenFolder(), p => CanOpenFolder());
}
private string _foldernameWithPath;
public string FoldernameWithPath
{
get { return _foldernameWithPath; }
set
{
if (value == _foldernameWithPath)
{
return;
}
else
{
_foldernameWithPath = value;
OnPropertyChanged("FoldernameWithPath");
}
}
}
public bool CanOpenFolder()
{
return true;
}
private void OpenFolder()
{
FolderBrowserDialog openFolderDialog = new FolderBrowserDialog();
if (openFolderDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK && OpenFolderCommand.CanExecute(openFolderDialog.SelectedPath))
{
OpenFolderCommand.Execute(openFolderDialog.SelectedPath);
FoldernameWithPath = openFolderDialog.SelectedPath;
}
}
You should not call the command from the delegated method (OpenFolder()). The command does nothing else than to execute the delegated method when Execute is called, which is done automatically when you click the button.
Also ICommand.CanExecute() is typically called automatically by WPF itself and based on the result in only enables, resp disables the button. You rarely call the CanExecute by yourself in ViewModel. In your case, you want the button always enabled, so you can skip CanExecute, or use p => true expression.
This should work
xaml:
<TextBox Text="{Binding Path=FoldernameWithPath}" IsReadOnly="True" />
<Button Content="Browse" Command="{Binding OpenFolderCommand}" />
viewmodel:
public ICommand OpenFolderCommand {get;} = new RelayCommand(p => OpenFolder());
private string _foldernameWithPath;
public string FoldernameWithPath
{
get { return _foldernameWithPath; }
set
{
if (value == _foldernameWithPath) return
_foldernameWithPath = value;
OnPropertyChanged("FoldernameWithPath");
}
}
public void OpenFolder()
{
FolderBrowserDialog openFolderDialog = new FolderBrowserDialog();
if (openFolderDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
FoldernameWithPath = openFolderDialog.SelectedPath;
}
}
Do the following changes,
In View.xaml
<Button Content="Browse" Command="{Binding OpenFolderCommand}"/>
In ViewModel.cs
public bool CanOpenFolder()
{
return true;
}
private void OpenFolder()
{
FolderBrowserDialog openFolderDialog = new FolderBrowserDialog();
if (openFolderDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK && OpenFolderCommand.CanExecute(openFolderDialog.SelectedPath))
{
//OpenFolderCommand.Execute(openFolderDialog.SelectedPath);
FoldernameWithPath = openFolderDialog.SelectedPath;
}
}
I would write the command like this:
public ICommand OpenFolderCommand { get; private set; }
public MyViewModel()
{
this.OpenFolderCommand = new RelayCommand(a=> this.OpenFolder(),p=> CanOpenFolder());
}
I am trying to use Prism in a C# to but I seem to have set it up so that it binds to items in my model and not my viewmodel. It is a short program that is more of a learning tool that anything. When I move the items to the Viewmodel the SetProperty's don't seem to notify the view of the change.
Any thoughts as to how I have this setup backwards?
Model:
namespace XMLValueModifier_Threaded_WPF.Models
{
public class XMLReadFileModel : BindableBase
{
private string _XMLMasterFile2 = "0";
public string XMLGetFileName()
{
if (_XMLMasterFile2 != "1")
{
Microsoft.Win32.OpenFileDialog _XMLMasterFileDialog = new Microsoft.Win32.OpenFileDialog();
_XMLMasterFileDialog.DefaultExt = "xml";
_XMLMasterFileDialog.Filter = "xml Files (*.xml; *.XML) | *.xml; *.XML";
Nullable<bool> result = _XMLMasterFileDialog.ShowDialog();
if (result == true)
{
return _XMLMasterFileDialog.FileName;
}
return "";
}
else
{
return "";
}
}
}
}
ViewModel:
namespace XMLValueModifier_Threaded_WPF.ViewModels
{
public class MainDialogueViewModel : BindableBase
{
private string _XMLMasterFile;
public ICommand MasterFileLocation
{
get;
set;
}
public ICommand XMLFileLocation
{
get;
set;
}
public string XMLMasterFile
{
get
{
return _XMLMasterFile;
}
set
{
SetProperty(ref _XMLMasterFile, value);
}
}
private XMLReadFileModel xmlReadFileModel = new XMLReadFileModel();
public MainDialogueViewModel()
{
XMLReadFileModel xmlReadFileModel = new XMLReadFileModel();
Message = "example message";
XMLMasterFile = "example File";
this.MasterFileLocation = new DelegateCommand(chooseFile, canChooseFile);
this.XMLFileLocation = new DelegateCommand(chooseFile, canChooseFile);
}
public void masterfilelocation()
{
MessageBox.Show("i am here");
return;
}
private void chooseFile()
{
XMLMasterFile = xmlReadFileModel.XMLGetFileName();
}
private bool canChooseFile()
{
if (XMLMasterFile != null)
{
return true;
}
else
{
return true;
}
}
}
}
XAML:
<Window x:Class="XMLValueModifier_Threaded_WPF.Views.MainDialogue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:XMLValueModifier_Threaded_WPF.ViewModels" Width="625" Height="452"
>
<Grid Margin="0,-24,0,-3">
<TextBox x:Name="Textbox1" Text="{Binding MainDialogueViewModel.XMLMasterFile,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="25,120,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="425"/>
<TextBox x:Name="Textbox2" Text="{Binding MainDialogueViewModel.XMLFiles,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="25,188,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="425" RenderTransformOrigin="0.506,1.565"/>
<Label Content="Location of Master XML File" HorizontalAlignment="Left" Margin="25,89,0,0" VerticalAlignment="Top"/>
<Label Content="Location of XML File(s)" HorizontalAlignment="Left" Margin="25,157,0,0" VerticalAlignment="Top"/></GRID>
Assuming you have your DataContext setup correctly to an instance of MainDialogueViewModel; you don't need to include MainDialogueViewModel in your binding. Simply bind to the property name XMLMasterFile. Also keep in mind that if the value isn't different, then nothing will be updated.
I'm working in WPF application with MVVM. Loading and Editing a image in imageSlider gives me error and exceptions.
Loading Image: Insufficient memory to handle
Editing Image: Sharing violation
ViewModel:
public class ImageList : INotifyPropertyChanged
{
#region Fields
private ObservableCollection<ImageItem> images = new ObservableCollection<ImageItem>();
private int selectedIndex;
private ImageItem currentImage;
#endregion Fields
#region Properties
public ObservableCollection<ImageItem> Images
{
get { return images; }
set { images = value; }
}
public int SelectedIndex
{
get { return selectedIndex; }
set
{
if(value < Images.Count && value > -1)
{
selectedIndex = value; OnPropertyChanged();
CurrentImage = Images[selectedIndex];
}
}
}
public ImageItem CurrentImage
{
get { return currentImage; }
set { currentImage = value; OnPropertyChanged(); }
}
#endregion Properties
#region Public Methods
public void Next()
{
SelectedIndex ++;
}
public void Back()
{
SelectedIndex--;
}
#endregion Public Methods
#region Methods
public void AddNewImage()
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.gif, *.png, *.bmp, *.tif) | *.jpg; *.jpeg; *.jpe; *.gif; *.png, *.bmp, *.tif";
dlg.ShowDialog();
if(dlg.FileName != "")
{
Images.Add(new ImageItem() { URI = new Uri(dlg.FileName) });
SelectedIndex = Images.Count - 1;
}
}
#endregion Methods
#region Constructors
public ImageList()
{
_canExecute = true;
}
#endregion Constructors
#region Commands
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(() => AddNewImage(), _canExecute));
}
}
private bool _canExecute;
private ICommand nextCommand;
public ICommand NextCommand
{
get
{
if (nextCommand == null)
{
nextCommand = new CommandHandler(()=> OnNextCommand(), true);
}
return nextCommand;
}
set { nextCommand = value; }
}
private void OnNextCommand()
{
Next();
}
private ICommand backCommand;
public ICommand BackCommand
{
get
{
if(backCommand == null)
{
backCommand = new CommandHandler(() => OnBackCommand(), true);
}
return backCommand;
}
set { backCommand = value; }
}
private void OnBackCommand()
{
Back();
}
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(() => AddNewImage(), _canExecute));
}
}
private ICommand _EditImageCommand;
public ICommand EditImageCommand
{
get
{
return _EditImageCommand ?? (_EditImageCommand = new CommandHandler(obj => EditImage(obj), _canExecute));
}
}
#endregion Commands
public void EditImage(object obj)
{
string ss = ((ImageItem)obj).URI.AbsolutePath;
Process proc = Process.Start(ss);
if (proc != null)
{
proc.EnableRaisingEvents = true;
ProcessStartInfo startInfo = new ProcessStartInfo();
//startInfo.Verb = "edit";
startInfo.FileName = ("mspaint.exe");
proc.StartInfo = startInfo;
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged
}`
Model:
public class ImageItem
{
public Uri URI { get; set; }
private BitmapSource _Source;
public BitmapSource Source
{
get
{
try
{
if (_Source == null) _Source = new BitmapImage(URI);//lazy loading
}
catch (Exception)
{
_Source = null;
}
return _Source;
}
}
public void Save(string filename)
{
var img = BitmapFrame.Create(Source);
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(img);
using (var saveStream = System.IO.File.OpenWrite(filename))
encoder.Save(saveStream);
}
}
XAML
<DockPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="200"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Content="<" Command="{Binding BackCommand}" Width="25" Grid.Row="0" Grid.Column="0"/>
<Button DockPanel.Dock="Right" Content=">" Command="{Binding NextCommand}" Width="25" Grid.Row="0" Grid.Column="2"/>
<Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="0" Grid.Column="1">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit Image" Command="{Binding EditImageCommand, Source={StaticResource SliderViewModel}}" CommandParameter="{Binding CurrentImage}"></MenuItem>
</ContextMenu>
</Image.ContextMenu>
</Image>
<Button Content="Add" Command="{Binding ClickCommand}" Height="30" Width="50" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4"></Button>
<!--<Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="2" Grid.Column="1"/>-->
</Grid>
</DockPanel>
The code above works fine with the imageSlider(Next and Back). But during edit it opens in mspaint and when i save it throws me Sharing violation error. Tired few solutions posted in SO here and here . When trying these i can able to edit the image and save without any error. But when using the Slider control (Next and Back) and when it loads heavier images, frequently it throws me Insufficient memory to handle exception.
So now i need to eliminate both these issues. Kindly help.
You need to do things a little differently for an editor to a viewer
your Model needs to be updated to a ViewModel as it will be changing ( you can use the model to build the ViewModel, however in this case there is so little functionality in the Model that you might as well use the FIleInfo class as your model)
public class ImageEditor: IDisposable,INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<FileInfo> images = new List<FileInfo>();
private FileInfo _ImageFile;
public static readonly PropertyChangedEventArgs FilenameProperty = new PropertyChangedEventArgs(nameof(ImageFile));
public FileInfo ImageFile
{
get { return _ImageFile; }
set
{
_ImageFile = value;
Strokes.Clear();
PropertyChanged?.Invoke(this, ImageFrameProperty);
}
}
private int selectedIndex;
public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs(nameof(SelectedIndex));
public int SelectedIndex
{
get { return selectedIndex; }
private set
{
if (value < images.Count && value > -1)
{
selectedIndex = value;
PropertyChanged?.Invoke(this, SelectedIndexProperty);
ImageFile = images[selectedIndex];
Load();
}
}
}
MemoryStream mem;
private BitmapSource _ImageFrame;
public static readonly PropertyChangedEventArgs ImageFrameProperty = new PropertyChangedEventArgs(nameof(ImageFrame));
public BitmapSource ImageFrame
{
get { return _ImageFrame; }
set
{
_ImageFrame = value;
PropertyChanged?.Invoke(this, ImageFrameProperty);
}
}
public StrokeCollection Strokes { get; } = new StrokeCollection();
public void Open(FileInfo file)
{
images.Add(file);
SelectedIndex = images.Count - 1;
}
public void Next()
{
SelectedIndex++;
}
public void Back()
{
SelectedIndex--;
}
public void Load()
{
ImageFile.Refresh();
if (ImageFile.Exists)
{
if (mem != null)
{
mem.Dispose();
}
using (var stream = ImageFile.OpenRead())
{
mem = new MemoryStream();
stream.CopyTo(mem);
}
ImageFrame = BitmapFrame.Create(mem);
}
}
public void Dispose()
{
if (mem != null)
{
mem.Dispose();
}
ImageFrame = null;
}
public void Save()
{
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(ImageFrame, new Rect(0, 0, ImageFrame.Width, ImageFrame.Height));
foreach (var item in Strokes)
{
item.Draw(drawingContext);
}
drawingContext.Close();
Strokes.Clear();
var width = ImageFrame.Width;
var height = ImageFrame.Height;
var bitmap = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(drawingVisual);
ImageFrame = bitmap;
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(ImageFrame));
using (var stream = ImageFile.OpenWrite())
{
encoder.Save(stream);
}
}
}
}
note: as we are using a memory stream with out the using statement then its a good idea to us the IDisposable interface for clean up
what this is doing is creating a memory resident bitmap then using that as a Frame, this removes the openRead lock on on the file that you get with a normal bitmap with a URI
next we have the Editor itself
<Window x:Class="ImageDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ImageDemo"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Closing="Window_Closing" >
<Window.DataContext>
<local:ImageEditor x:Name="editor" />
</Window.DataContext>
<DockPanel>
<Menu DockPanel.Dock="Top" >
<MenuItem Header="File" >
<MenuItem Command="ApplicationCommands.Open" />
<MenuItem Command="ApplicationCommands.Save" />
</MenuItem>
<MenuItem Command="ApplicationCommands.Undo" />
<ComboBox SelectedValue="{Binding DefaultDrawingAttributes.Color, ElementName=inkCanvas}">
<Color>White</Color>
<Color>Black</Color>
<Color>Yellow</Color>
<Color>Red</Color>
<Color>Cyan</Color>
<Color>SpringGreen</Color>
<ComboBox.ItemTemplate>
<DataTemplate>
<Rectangle Width="15" Height="15">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Mode=OneWay}" />
</Rectangle.Fill>
</Rectangle>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Menu>
<Button Content="<" Click="Back_Click"/>
<Button Content=">" DockPanel.Dock="Right" Click="Next_Click"/>
<Grid HorizontalAlignment="Left" VerticalAlignment="Top" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
<Image Source="{Binding ImageFrame}" Stretch="None"/>
<InkCanvas x:Name="inkCanvas" Background="Transparent" Strokes="{Binding Strokes}" >
<InkCanvas.DefaultDrawingAttributes>
<DrawingAttributes x:Name="DrawSetting" />
</InkCanvas.DefaultDrawingAttributes>
</InkCanvas>
</Grid>
</DockPanel>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, execSave, hasChanged));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Open, execOpen));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, execUndo, hasChanged));
}
private void execOpen(object sender, ExecutedRoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
if(dlg.ShowDialog() ?? false)
{
editor.Open(new System.IO.FileInfo(dlg.FileName));
}
e.Handled = true;
}
private void hasChanged(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = editor.Strokes.Count > 0;
e.Handled = true;
}
private void execUndo(object sender, ExecutedRoutedEventArgs e)
{
editor.Strokes.Remove(editor.Strokes.Last());
e.Handled = true;
}
private void execSave(object sender, ExecutedRoutedEventArgs e)
{
editor.Save();
e.Handled = true;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
editor.Dispose();
}
private void Next_Click(object sender, RoutedEventArgs e)
{
editor.Next();
}
private void Back_Click(object sender, RoutedEventArgs e)
{
editor.Back();
}
}
Note if you are doing any scaling/stretching on the image then you need to reverse the operation on the Strokes before rendering them or or the edits will be in relation to the screen not the image
I have a username and password box.
Underneath it I have a button.
When I click that button I want to analyse what has been put into the username and password box.
How do I do this with mvvm light?
This is where I am:
XAML
...DataContext="{Binding Main, Source={StaticResource Locator}}">...
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0">
<TextBlock HorizontalAlignment="Left" Margin="10,0,0,0" TextWrapping="Wrap" Text="Username" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="72" Margin="0,27,0,0" TextWrapping="Wrap" Text="{Binding Username}" VerticalAlignment="Top" Width="456"/>
<TextBlock HorizontalAlignment="Left" Margin="10,99,0,0" TextWrapping="Wrap" Text="Password" VerticalAlignment="Top"/>
<PasswordBox HorizontalAlignment="Left" Height="72" Margin="0,126,0,0" Password="{Binding Password}" VerticalAlignment="Top" Width="456"/>
<Button Content="Log in" HorizontalAlignment="Center" Margin="167,203,169,0" VerticalAlignment="Top" Command="{Binding LogInCommand}"/>
</Grid>
View Model
public class MainViewModel : ViewModelBase
{
public LoginCredentials LoginCredentials { get; set; }
public ICommand LogInCommand { get; private set; }
public MainViewModel()
{
LoginCredentials = new LoginCredentials();
LogInCommand = new RelayCommand(this.OnLogInCommand);
}
private void OnLogInCommand()
{
string testUsername = Username;
string testPassword = Password;
}
#region Properties
public string Username
{
get { return LoginCredentials.Username; }
set { LoginCredentials.Password = value; }
}
public string Password
{
get { return LoginCredentials.Password; }
set { LoginCredentials.Password = value; }
}
#endregion
}
MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
}
}
what is happening at the moment:
When I click my button, the LogInCommand is run and it fires my method OnLoginCommand. I have put a break point on the testUsername declaration to see if when the button is clicked, the username and password reflect what has been put in; they are both empty. What must I do to make sure these are updated as someone is typing or when the button is pressed or however it works???
I have now spent about 4 weeks learning mvvm and trying to get a simple click event and binding to work. This is simply not making sense... doh. Thanks for any help!
P.S - Is MVVM light too confusing for new comers? the documentation is so.. light on detail. No examples :(
View
Windows Phone doesn't contain "UpdateSourceTrigger=PropertyChanged". You have to use "Explicit" and manually call "UpdateSource" in code behind, otherwise value of TextBox/PasswordBox will be raise when TextBox/PasswordBox lost focus.
And don't forget to set "Mode=TwoWay".
<TextBox
Text="{Binding Path=Username, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
TextChanged="TextBoxTextChanged" />
<PasswordBox
Password="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
PasswordChanged="PasswordBoxPasswordChanged" />
<Button
Command="{Binding Path=LogInCommand}"
Content="Log in" />
View - code behind
private void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox pb = sender as PasswordBox;
if (pb != null)
{
pb.GetBindingExpression(PasswordBox.PasswordProperty).UpdateSource();
}
}
private void TextBoxTextChanged(object sender, TextChangedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
tb.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
ViewModel
Fields
private RelayCommand _logInCommand;
private string _password;
private string _username;
Properties
public bool CanExecuteLogInCommand
{
get
{
return !string.IsNullOrWhiteSpace(this.Username) && !string.IsNullOrWhiteSpace(this.Password);
}
}
public RelayCommand LogInCommand
{
get
{
// or you can create instance in constructor: this.LogInCommand = new RelayCommand(this.ExecuteLogInCommand, () => this.CanExecuteLogInCommand);
return this._logInCommand ?? (this._logInCommand = new RelayCommand(this.ExecuteLogInCommand, () => this.CanExecuteLogInCommand));
}
}
public string Username
{
get { return this._username; }
set
{
// a) shorter alternative -> "True if the PropertyChanged event has been raised, false otherwise"
if (this.Set(() => this.Username, ref this._username, value))
{
// raise CanExecuteLogInCommand
this.LogInCommand.RaiseCanExecuteChanged();
}
// b) longer alternative
//if (value == this._username) { return; }
//this._username = value;
//this.RaisePropertyChanged(() => this.Username);
//this.LogInCommand.RaiseCanExecuteChanged();
}
}
public string Password
{
get { return this._password; }
set
{
if (this.Set(() => this.Password, ref this._password, value))
{
this.LogInCommand.RaiseCanExecuteChanged();
}
}
}
Methods
private void ExecuteLogInCommand()
{
// .... = this.Username;
// .... = this.Password;
}
Check this sample.
To get the View and ViewModel 'linked up' so that they are synchronized, you need to implement INotifyPropertyChanged (Encapsulated in ViewModelBase). i.e.:
private string userName;
public string UserName
{
get { return userName; }
set
{
if (value != userName)
{
userName = value;
RaisePropertyChanged("UserName");
}
}
}