Image edit and save Out of Memory Exception C# - c#

I'm working in a WPF application where I show my images in two places which means the same image gets loaded in two places. In one of the place the image will be shown along with other few images in a slider where it will be able to edit and save. If there is no image available in the location I should be showing a separate image Image not found which is not editable.
When I started working on the functionality I got the Used by another process exception during edit and save. So after searching I came up with a solution and now at a rare time I get the Out of memory exception when I click the Next or Previous or First or Last in slider. The slider is just an Image control with 4 buttons. When the buttons are clicked the below method is called. I'm not sure if there is any memory leaks.
bool NoImage = true;
private static readonly object _syncRoot = new object();
private BitmapSource LoadImage(string path)
{
lock (_syncRoot) //lock the object so it doesn't get executed more than once at a time.
{
BitmapDecoder decoder = null;
try
{
//If the image is not found in the folder, then show the image not found.
if (!File.Exists(path) && (path != null))
{
System.Drawing.Bitmap ss = XXXX.Resources.ImageNotFound;
var stream = new System.IO.MemoryStream();
if (!File.Exists(Path.GetTempPath() + "ImageNotFound.jpg"))
{
FileStream file = new FileStream(Path.GetTempPath() + "ImageNotFound.jpg", FileMode.Create, FileAccess.Write);
ss.Save(stream, ImageFormat.Jpeg);
stream.Position = 0;
stream.WriteTo(file);
file.Close();
stream.Close();
}
path = Path.Combine(Path.GetTempPath(), "ImageNotFound.jpg");
NoImage = false;
}
else
{
if (!EnableForEdit)
NoImage = false;
else
NoImage = true;
}
if (!string.IsNullOrEmpty(path) && (!NoImage || File.Exists(path)))
{
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
return decoder.Frames.FirstOrDefault();
}
else
return null;
}
catch (OutOfMemoryException ex)
{
MessageBox.Show("Insufficient memory to handle the process. Please try again later.", "Application alert");
return null;
}
catch (Exception ex)
{
// Error handling.
throw new ApplicationException(ex.Message);
}
finally
{
decoder = null;
GC.WaitForFullGCComplete(1000);
GC.Collect(0, GCCollectionMode.Forced);
}
}
}
<Image x:Name="viewImage" Grid.Row="2" Height="100" Width="135" Source="{Binding DisplayImage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" />
If my approach is wrong, let me know where should I do the change or if there is any simpler way to do. Kindly help.
Note: The images which are loaded is above 5Mb

Firstly when ever you create a stream you need to dispose of it once you are finished with it (Note Close does not Dispose, but Dispose does close), if not then the stream stays in memory consuming resources
so your code should look as follows
using(var stream = new System.IO.MemoryStream())
{
if (!File.Exists(Path.GetTempPath() + "ImageNotFound.jpg"))
{
using(FileStream file = new FileStream(Path.GetTempPath() + "ImageNotFound.jpg", FileMode.Create, FileAccess.Write))
{
ss.Save(stream, ImageFormat.Jpeg);
stream.Position = 0;
stream.WriteTo(file);
}
}
}
Second you need to reduce your apps memory impact
to do that i would suggest leveraging the functionality already in WPF here is a quick example of how you should do this
Your 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)
}
}
Your View Model
public class ImageList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ImageItem> Images { get; } = new ObservableCollection<ImageItem>();
private int _SelectedIndex;
// c# >= 6
public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs(nameof(SelectedIndex));
// c# < 6
// public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs("SelectedIndex");
public int SelectedIndex
{
get { return _SelectedIndex; }
set
{
_SelectedIndex = value;
// c# >= 6
PropertyChanged?.Invoke(this, SelectedIndexProperty);
PropertyChanged?.Invoke(this, CurrentImageProperty);
// c# < 6
// var handler = PropertyChanged;
// if(handler !=null)
// {
// handler (this, SelectedIndexProperty);
// handler (this, CurrentImageProperty);
// }
}
}
// c# >= 6
public static readonly PropertyChangedEventArgs CurrentImageProperty = new PropertyChangedEventArgs(nameof(CurrentImage));
// c# < 6
// public static readonly PropertyChangedEventArgs CurrentImageProperty = new PropertyChangedEventArgs("CurrentImage");
public ImageItem CurrentImage => Images.Count>0 ? Images[SelectedIndex] : null;
public void Next()
{
if (SelectedIndex < Images.Count - 1)
SelectedIndex++;
else
SelectedIndex = 0;
}
public void Back()
{
if (SelectedIndex == 0)
SelectedIndex = Images.Count - 1;
else
SelectedIndex--;
}
public void Add(string Filename)
{
Images.Add(new ImageItem() { URI= new Uri(Filename) });
// c# >= 6
PropertyChanged?.Invoke(this, CurrentImageProperty);
// c# < 6
// var handler = PropertyChanged;
// if(handler !=null)
// {
// handler (this, CurrentImageProperty);
// }
}
}
and Finally your View
<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">
<Window.Resources>
<BitmapImage x:Key="NotFound" UriSource="C:\...\NotFound.png"/>
</Window.Resources>
<Window.DataContext>
<local:ImageList/>
</Window.DataContext>
<DockPanel>
<Button Content="<" Click="Back_Click"/>
<Button DockPanel.Dock="Right" Content=">" Click="Next_Click"/>
<Image Source="{Binding CurrentImage.Source, Mode=OneWay,
TargetNullValue={StaticResource NotFound},
FallbackValue={StaticResource NotFound}}"/>
</DockPanel>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//c# >= 6
private ImageList list => DataContext as ImageList;
//c# < 6
//private ImageList list {get{ return DataContext as ImageList;}}
private void Next_Click(object sender, RoutedEventArgs e)
{
list.Next();
}
private void Back_Click(object sender, RoutedEventArgs e)
{
list.Back();
}
}
note:
because the Model is separate to the View you can show the same image in several places with no issue at all
also System.Drawing.Bitmap is not WPF compatible so you should use the WPF classes in System.Windows.Media.Imaging

Related

C# WPF binding bitmapImage to Image control [duplicate]

I'm trying to load a image asynchronously.
MainWindow code
public partial class MainWindow : Window
{
private Data data = new Data();
public MainWindow()
{
InitializeComponent();
this.DataContext = data;
}
private async void button_Click(object sender, RoutedEventArgs e)
{
data.Image = await Data.GetNewImageAsync();
}
}
Data class
public class Data : INotifyPropertyChanged
{
private BitmapImage _Image = new BitmapImage();
public BitmapImage Image { get { return _Image; } set { _Image = value; OnPropertyChanged("Image"); } }
public static BitmapImage GetNewImage()
{
return new BitmapImage(new Uri("http://www.diseno-art.com/news_content/wp-content/uploads/2012/09/2013-Jaguar-F-Type-1.jpg"));
}
public async static Task<BitmapImage> GetNewImageAsync()
{
return await Task.Run(() => GetNewImage());
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
WPF code
<Button Name="button" Click="button_Click">Image</Button>
<Image Grid.Row="1" Source="{Binding Path=Image, UpdateSourceTrigger=PropertyChanged}"></Image>
Problem
I get the exception:
System.ArgumentException: "Must create DependencySource on same Thread
as the DependencyObject."
... in this row:
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
But if i change BitmapImage to string this code works fine.
What am I doing wrong?
When a BitmapImage is created in a background thread, you have to make sure that it gets frozen before it is used in the UI thread.
You would have to load it yourself like this:
public static async Task<BitmapImage> GetNewImageAsync(Uri uri)
{
BitmapImage bitmap = null;
var httpClient = new HttpClient();
using (var response = await httpClient.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
using (var stream = new MemoryStream())
{
await response.Content.CopyToAsync(stream);
stream.Seek(0, SeekOrigin.Begin);
bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
bitmap.Freeze();
}
}
}
return bitmap;
}
Or shorter with BitmapFrame.Create, which returns an already frozen BitmapSource:
public static async Task<BitmapSource> GetNewImageAsync(Uri uri)
{
BitmapSource bitmap = null;
var httpClient = new HttpClient();
using (var response = await httpClient.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
using (var stream = new MemoryStream())
{
await response.Content.CopyToAsync(stream);
stream.Seek(0, SeekOrigin.Begin);
bitmap = BitmapFrame.Create(
stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
}
return bitmap;
}
Note that the second method requires to change the type of your Image property to BitmapSource (or even better, ImageSource), which would provide greater flexibility anyway.
An alternative method without any manual download might look like shown below. It also does not require to freeze the BitmatImage, because it is not created in a Task thread.
public static Task<BitmapSource> GetNewImageAsync(Uri uri)
{
var tcs = new TaskCompletionSource<BitmapSource>();
var bitmap = new BitmapImage(uri);
if (bitmap.IsDownloading)
{
bitmap.DownloadCompleted += (s, e) => tcs.SetResult(bitmap);
bitmap.DownloadFailed += (s, e) => tcs.SetException(e.ErrorException);
}
else
{
tcs.SetResult(bitmap);
}
return tcs.Task;
}

filling an ImageSource with a selector

i'm trying to fill up an image from a selector based on this https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/dependency-service/photo-picker
the selector works fine and the stream fills up. however i do not get the picture on my screen.
public Command SelectPictureCommand { get; }
public ImageSource ItemPic { get; set; }
SelectPictureCommand = new Command(execute: async () =>
{
if (IsBusy)
{
return;
}
IsBusy = true;
try
{
Stream stream = await DependencyService.Get<IPhotoPickerService>().GetImageStreamAsync();
if (stream != null)
{
ItemPic = ImageSource.FromStream(() => stream);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
});
xaml
<StackLayout Orientation="Vertical" HorizontalOptions="End">
<Button Text="Select Picture" Command="{Binding SelectPictureCommand}"/>
<Image Source="{Binding ItemPic}" WidthRequest="300" HeightRequest="300"/>
</StackLayout>
You should implement the INotifyPropertyChanged interface in your ViewModel or Model:
ViewModels generally implement the INotifyPropertyChanged interface,
which means that the class fires a PropertyChanged event whenever one
of its properties changes. The data binding mechanism in Xamarin.Forms
attaches a handler to this PropertyChanged event so it can be notified
when a property changes and keep the target updated with the new
value.
public class myViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public Command SelectPictureCommand { get; }
public ImageSource itemPic { get; set; }
public ImageSource ItemPic
{
set
{
if (itemPic != value)
{
itemPic = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ItemPic"));
}
}
}
get
{
return itemPic;
}
}
public myViewModel() {
SelectPictureCommand = new Command(execute: async () =>
{
//if (IsBusy)
//{
// return;
//}
//IsBusy = true;
try
{
Stream stream = await DependencyService.Get<IPhotoPickerService>().GetImageStreamAsync();
if (stream != null)
{
ItemPic = ImageSource.FromStream(() => stream);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
//IsBusy = false;
}
});
}
}
I uploaded a sample here and you can check. Feel free to ask me any problem if you have.
You can use CrossMedia Plugin, example:
await CrossMedia.Current.Initialize();
var file = await CrossMedia.Current.PickPhotoAsync();
var filestream = file.GetStream();
byte[] buff = ConverteStreamToByteArray(filestream);
image.Source = ImageSource.FromStream(buff);
filestream.Dispose();

C# Windows 10 Universal app - MVVM refresh

I developed an application on Windows 10 Universal App who use MVVM but I have a big problem with it.
I would add an ObservableCollection item(created on a second window) to the MVVM and then, show the new item on the ListView of MainPage but it doesn't refresh!
The 2 windows are always open
http://i.stack.imgur.com/WSo6v.jpg
The code of MVVMList.cs
public class MVVMList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<THEFile> onglets_cache = new ObservableCollection<THEFile>();
public ObservableCollection<THEFile> onglets_list
{
get
{
return onglets_cache;
}
set
{
onglets_cache = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this,
new PropertyChangedEventArgs("onglets_list"));
}
}
public MVVMList()
{
onglets_list = new ObservableCollection<THEFile>();
Fonctions fonctions = new Fonctions();
fonctions.LoadOnglets(onglets_cache);
}
}
The code of the second page(always open) - CreateFile.xaml.cs
private void create_butt_Click(object sender, RoutedEventArgs e)
{
Fonctions fonc = new Fonctions(); MVVMList main = new MVVMList();
fonc.SetupNew(main.onglets_list, "test" + ".php", "");
}
//SetupNew on Fonctions.cs
public async void SetupNew(ObservableCollection<THEFile> list, string name, string content)
{
FolderPicker folderpick = new FolderPicker();
folderpick.ViewMode = PickerViewMode.List;
folderpick.FileTypeFilter.Add(".html"); folderpick.FileTypeFilter.Add(".htm"); folderpick.FileTypeFilter.Add(".HTML");
folderpick.FileTypeFilter.Add(".php"); folderpick.FileTypeFilter.Add(".PHP");
folderpick.FileTypeFilter.Add(".css"); folderpick.FileTypeFilter.Add(".CSS");
folderpick.FileTypeFilter.Add(".js"); folderpick.FileTypeFilter.Add(".JS");
StorageFolder storage_file = await folderpick.PickSingleFolderAsync();
if (storage_file != null)
{
MainPage vm = new MainPage();
list.Add(new THEFile { NameOfFile = name, PathOfFile = storage_file.Path + "\\" + name, CodeOfFile = content, already_opened = false, line = 0 });
string path = storage_file.Path + #"\" + name;
StorageFile file_create = await storage_file.CreateFileAsync(name, CreationCollisionOption.GenerateUniqueName);
Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.Add(file_create);
SaveOnglets(list);
}
}
And on the MainPage.xaml (always open)
...
<ListView x:Name="onglets" x:FieldModifier="public" ItemTemplate="{StaticResource Templa}" ItemsSource="{Binding onglets_list}" SelectionChanged="onglets_SelectionChanged" Margin="0,117,0,57" Visibility="Visible" ContainerContentChanging="onglets_ContainerContentChanging">
...
Thank you!
In your XAML, try using a Collection View Source.
Add this to the top of your xaml:
<Page.Resources>
<CollectionViewSource x:Name="MakesCollectionViewSource" IsSourceGrouped="True"/>
</Page.Resources>
Set your ListView:
ItemsSource="{Binding Source={StaticResource MakesCollectionViewSource}}"
Then in your code when you have a List of items assign it using
MakesCollectionViewSource.Source = /* Some List<GroupInfoList<object>> that is generated from onglets_list*/
I create my List like this but it may not be relevant because this is to make all of my object names alphabetical:
internal List<GroupInfoList<object>> GetGroupsByLetter()
{
var groups = new List<GroupInfoList<object>>();
var query = from item in MakeList
orderby ((Make)item).MakeName
group item by ((Make)item).MakeName[0] into g
select new { GroupName = g.Key, Items = g };
foreach (var g in query)
{
var info = new GroupInfoList<object>();
info.Key = g.GroupName;
foreach (var item in g.Items)
{
info.Add(item);
}
groups.Add(info);
}
return groups;
}
public class GroupInfoList<T> : List<object>
{
public object Key { get; set; }
public new IEnumerator<object> GetEnumerator()
{
return (System.Collections.Generic.IEnumerator<object>)base.GetEnumerator();
}
}
where MakeList is my observable collection and Make are the objects in the collection and MakeName is a string I am trying to alphabetize
And then call using
MakesCollectionViewSource.Source = GetGroupsByLetter();
If I understand your code and requirements correctly, I think part of the problem is that you "new up" your MVVMList and your MainPage everytime you click the create button.
So, without getting into suggestions about using MVVM Light and an IOC container, you could quickly accomplish what you're trying to do by making your MVVMList class a singleton and having your MainPage use it for a data context. When your other window adds to the MVVMList.onglets collection, it will be immediately reflected in your currently open MainPage. Let me know if you need some code snippets. Good luck!
[Edit below]
I had a few minutes left on lunch, so here is an over-simplified example. Again, without getting into what MVVM is and is not. Personally, I would do this differently, but that would be outside the scope of your question. Full disclosure - this is in WPF, but same logic applies, I just don't have Windows 10 on the PC that I'm using. I also simplified the collection to be of type string. This is not intended to copy/paste into your code as it will not work in your example - but should easily transfer.
MVVMList class:
public class MVVMList: INotifyPropertyChanged
{
//Singleton section
private static MVVMList instance;
private MVVMList() { }
public static MVVMList Instance
{
get
{
if (instance == null)
{
instance = new MVVMList();
}
return instance;
}
}
//end singleton section
private ObservableCollection<string> _onglets = new ObservableCollection<string>();
public ObservableCollection<string> Onglets
{
get { return _onglets; }
set
{
if (_onglets != value)
{
_onglets = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this,
new PropertyChangedEventArgs("onglets_list"));
}
}
}
//INotify implementation
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage:
<ListView x:Name="onglets" x:FieldModifier="public" ItemsSource="{Binding Onglets}" />
MainPage.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = MVVMList.Instance;
Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var x = new CreateWindow();
x.Show();
}
}
CreateWindow.cs:
private void CreateButton_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(StringTextBox.Text))
{
MVVMList.Instance.Onglets.Add(StringTextBox.Text);
}
}

WPF Image control is not automatic update from ViewModel

I am new to WPF and C#, I try to implement an Image control, which is updated whenever there is new data coming from serial port, but failed after a lot of attempts. It just shows the black image at the beginning and when there is new data coming, the Image control is not updated (ImageSource in ImageViewModel was not updated and event NotifyPropertyChanged was not fired). Can anyone help me out?
I have 3 classes and 1 Usercontrol. They are ImageViewModel, ImageConverter, Control and UserControl and UserControl code behind.
My ImageViewModel class
public class ImageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private System.Drawing.Image _img;
public System.Drawing.Image ImageSource
{
get { return _img; }
set { _img = value; NotifyPropertyChanged("ImageSource"); }
}
public ImageViewModel()
{
ImageSource = new System.Drawing.Bitmap(320, 240);
}
}
My ImageConverter Class
[ValueConversion(typeof(System.Drawing.Image), typeof(System.Windows.Media.ImageSource))]
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// empty images are empty...
if (value == null) { return null; }
else
{
var image = (System.Drawing.Image)value;
// Winforms Image we want to get the WPF Image from...
var bitmap = new System.Windows.Media.Imaging.BitmapImage();
bitmap.BeginInit();
MemoryStream memoryStream = new MemoryStream();
// Save to a memory stream...
image.Save(memoryStream, ImageFormat.Bmp);
// Rewind the stream...
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
bitmap.StreamSource = memoryStream;
bitmap.EndInit();
return bitmap;
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
My Control class
public void OpenPort()
{
try
{
SerialPort.Open();
SerialPort.DataReceived += new SerialDataReceivedEventHandler(receivedDataFromSerialPort);
SerialPort.DtrEnable = true;
SerialPort.RtsEnable = true;
}
catch (Exception e)
{
throw e;
}
}
public void ClosePort()
{
try
{
SerialPort.Close();
SerialPort.DataReceived -= new SerialDataReceivedEventHandler(receivedDataFromSerialPort);
}
catch (Exception ex)
{
throw ex;
}
}
ImageViewModel ivm = new ImageViewModel();
private void receivedDataFromSerialPort(object sender, SerialDataReceivedEventArgs e)
{
System.Drawing.Image tmp = new Bitmap(320,240);
//other codes
ivm.ImageSource = tmp;
}
My Usercontrol
<UserControl.Resources>
<imgcv:ImageConverter x:Key="imageConverter" />
</UserControl.Resources>
<UserControl.DataContext>
<viewmodel:ImageViewModel/>
</UserControl.DataContext>
<StackPanel Height="240" Width="300">
<Image Width="320" Height="240" Source="{Binding Path=ImageSource,IsAsync=True, Converter={StaticResource imageConverter},ConverterParameter=ImageSource, UpdateSourceTrigger=LostFocus}" />
</StackPanel>
UserControl code behind
public partial class UserControls : System.Windows.Controls.UserControl
{
Control ctrl = new Control();
//other codes
ctrl.OpenPort();
//other codes
ctrl.ClosePort();
}
Thanks a lot!
Why would you need a converter? Your User control can be created this way:
<UserControl Name="userControl1">
<StackPanel Height="240" Width="300">
<Image Width="320" Height="240" Source="{Binding Source}"/>
</StackPanel>
Your ViewModel class can be as follows:
public class ImageViewModel : ViewModelBase
{
ImageSource _source;
public ImageSource Source
{
get { return _source; }
set
{
_source = value;
OnPropertyChanged("Source");
}
}
public ImageViewModel()
{
//Initialize Source using new bitmap or any other way
}
}
ViewModelBase will implement INotifyPropertyChanged and the OnPropertyChanged method should be present in ViewModelBase.
This should work. I just came across in your question that your image will be loaded asynchronously. In that case you can update the "Source" using the Dispatcher.
The data context can be assigned in the following way:
ImageViewModel imageViewModel= new ImageViewModel();
userControl1.DataContext = imageViewModel;
There is one more method to assign data context which I use most of the time:
1)Bind a Window control(MainWindow)which encloses the user control to a MainWindowViewModel
2)MainWindowViewModel will have a Visibility parameter to control the User Controls Visibility.
3)<usercontrol DataContext="{Binding imageViewModel}" Visibility="{Binding imageViewVisibility}"/>
The data context parameter will be present in MainWindowViewModel.
4) MainWindowViewModel:ViewModelbase
{
ImageViewModel _viewModel;
public ImageViewModel imageViewModel
{
get{ return _viewModel; }
set
{
_viewModel=value;
OnPropertyChanged("imageViewModel");
}
}
It looks like the serial port handling class does not need to be derived from Control. It could be an ordinary class, which gets the view model instance passed to its constructor:
public class SerialPortController
{
private ImageViewModel ivm;
public SerialPortController(ImageViewModel ivm)
{
this.ivm = ivm;
}
public void OpenPort()
{
...
}
public void ClosePort()
{
...
}
private void receivedDataFromSerialPort(object sender, SerialDataReceivedEventArgs e)
{
...
}
}
Your UserControl would now use the SerialPortController like this:
public partial class UserControls : System.Windows.Controls.UserControl
{
private SerialPortController ctrl;
public UserControls()
{
InitializeComponent();
// pass ImageViewModel from the UserControl's DataContext
ctrl = new SerialPortController(DataContext as ImageViewModel);
}
//other code
ctrl.OpenPort();
//other code
ctrl.ClosePort();
}

Data binding main window's title to view model's property

I have a main window with following code:
<Window x:Class="CAMXSimulator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:View="clr-namespace:CAMXSimulator.View"
xmlns:ViewModel="clr-namespace:CAMXSimulator.ViewModel"
Icon="Resources/Images/Tractor.png"
Title="{Binding WindowTitle}"
Height="400" Width="600">
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:LogParserViewModel}">
<View:LogView />
</DataTemplate>
</Window.Resources>
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border CornerRadius="5" BorderBrush="SteelBlue" BorderThickness="2" Grid.Row="2" Margin="0,5,5,0" >
<View:LogView />
</Border>
</Grid>
</Window>
in the LogParserViewModel.cs class i have the following
EDIT:
class LogParserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// public event PropertyChangedEventHandler PropertyChanged1;
private IDbOperations _camxdb;
#region private_virables
private string _vManageLogFile;
private string _camxNodes;
private IEnumerable<Tuple<string, string>> _camxnodesAsTuple;
RelayCommand _clearFieldscommand;
RelayCommand _runsimulationcommand;
private string _currentProfileName;
#endregion
#region Getters\Setters
public string CurrentProfileName
{
get { return _currentProfileName; }
set
{
_currentProfileName = value;
OnPropertyChanged("CurrentProfileName");
OnPropertyChanged("WindowTitle");
}
}
public string VManageLogFile
{
get { return _vManageLogFile; }
set { _vManageLogFile = value;
if(null != PropertyChanged)
{
// PropertyChanged(this, new PropertyChangedEventArgs("VManageLogFile"));
OnPropertyChanged("VManageLogFile");
}
}
}
public string CamxNodes
{
get { return _camxNodes; }
set
{
_camxNodes = value;
if (null != PropertyChanged)
{
//PropertyChanged1(this, new PropertyChangedEventArgs("CamxNodes"));
OnPropertyChanged("CamxNodes");
}
}
}
#endregion
protected void OnPropertyChanged(string name)
{
// PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#region Constructors
public LogParserViewModel()
{
// PropertyChanged1 = new PropertyChangedEventHandler();
//PropertyChanged += UpdateCamxWindowEvent;
PropertyChanged += (s, e) => { if (e.PropertyName == "VManageLogFile") UpdateCamxWindowEvent(s, e); };
//creates a instance of database object
_camxdb = new DbOperations();
}
#endregion
#region Event_Hendlers
/// <summary>
/// This event is called when vManageLog window has changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void UpdateCamxWindowEvent(object sender, EventArgs e)
{
if (_vManageLogFile == null)
return;
//creates object of parser
var parser = new VManageLogParser(_vManageLogFile);
//returns a tuple of string string
_camxnodesAsTuple = parser.Parse();
//creates a string as we see it in the CAMX window of the simulator
CamxNodes = parser.CamxWindowText2(_camxnodesAsTuple);
MyLogger.Logger.Info("The Tabs been updated");
CurrentProfileName = "CAMX Simulator";
}
#endregion
#region Drag & DragOver
public void DragOver(DragEventArgs args)
{
// As an arbitrary design decision, we only want to deal with a single file.
if (IsSingleTextFile(args) != null) args.Effects = DragDropEffects.Copy;
else args.Effects = DragDropEffects.None;
// Mark the event as handled, so TextBox's native DragOver handler is not called.
args.Handled = true;
}
public void Drop(DragEventArgs args)
{
using (new WaitCursor())
{
// Mark the event as handled, so TextBox's native Drop handler is not called.
args.Handled = true;
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
StreamReader fileToLoad = new StreamReader(fileName);
VManageLogFile = fileToLoad.ReadToEnd();
// DisplaySFMFileContents.Text = fileToLoad.ReadToEnd();
fileToLoad.Close();
}
}
// If the data object in args is a single file, this method will return the filename.
// Otherwise, it returns null.
private string IsSingleTextFile(DragEventArgs args)
{
// Check for files in the hovering data object.
if (args.Data.GetDataPresent(DataFormats.FileDrop, true))
{
string[] fileNames = args.Data.GetData(DataFormats.FileDrop, true) as string[];
// Check fo a single file or folder.
if (fileNames.Length == 1)
{
// Check for a file (a directory will return false).
if (File.Exists(fileNames[0]))
{
//Check for the file extention , we look only for txt extentions
FileInfo info = new FileInfo(fileNames[0]);
if (info.Extension == ".txt")
{
MyLogger.Logger.Info("Name of file: " + fileNames[0]);
// At this point we know there is a single file text file.);
return fileNames[0];
}
}
}
}
MyLogger.Logger.Warn("Not a single file");
return null;
}
#endregion
#region ClearCommand
public ICommand ClearFieldsCommand
{
get
{
if (_clearFieldscommand == null)
_clearFieldscommand = new RelayCommand(
() => ClearFields(),
() => CanClearWindows);
return _clearFieldscommand;
}
}
void ClearFields()
{
VManageLogFile = null;
CamxNodes = null;
}
bool CanClearWindows
{
get { return (VManageLogFile != null ); }
}
#endregion
#region RunSimulation
public ICommand RunSimulationCommand
{
get
{
if (_runsimulationcommand == null)
_runsimulationcommand = new RelayCommand(
() => RunSimulation(),
() => CanRunSimulation);
return _runsimulationcommand;
}
}
void RunSimulation()
{
using (new WaitCursor())
{
try
{ //inserting the CAMX nodes to the table
foreach (var camxNode in _camxnodesAsTuple)
{
_camxdb.Insert(camxNode);
}
}
catch (Exception ex )
{
MyLogger.Logger.FatalException("Cannot Insert to Database" , ex);
}
}
}
bool CanRunSimulation
{
get { return !GlobalMethods.IsEmpty(_camxnodesAsTuple); }
}
#endregion
}
}
And I am trying to change the windows title by seeting it but nothing is happens any idea why ?
As I can't see from the current code what the DataContext of the main.xaml is, I'm going to take a guess that it is itself (not set to something else). I'm going to further go and say that your intent is to set the main.xaml's DataContext to a ViewModel:
XAML:
<Window x:Class="Namespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding WindowTitle}">
<!-- YOUR XAML -->
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
Where MainWindowViewModel.cs contains the property for the WindowTitle.
If you want a some other class to control the WindowTitle then you'll still need to have a ViewModel for your MainWindow (i.e. MainWindowViewModel.cs) that accepts messages somehow (events for tightly coupled, event aggregation for loosely coupled) to update that property.
Your property in ViewModel should be named WindowTitle instead of CurrentProfileName

Categories

Resources