Why my image update is not "binded"? - c#

I have a gird view of my objects "Sessions". On user selection, every Session's Image gets display in an Image on my GUI. I have this routine where, on user SelectedSession, it Deletes any existing image, Takes a new Image frameSource, Saves it locally and then sets the private member variable ImagePath as its path:
public Session SelectedSession { get { return selectedSession; } set { SetValue(ref selectedSession, value); } }
public BitmapSource SessionImage { get { return sessionImage; } private set { SetValue(ref sessionImage, value); } }
//..
public void TakeSessionImage(BitmapSource frameSource)
{
if (SelectedSession != null)
{
if (File.Exists(SelectedSession.ImagePath))
File.Delete(SelectedSession.ImagePath); // delete old - works
SelectedSession.ImagePath = FileStructure.CurrentSessionPath + "\\" + SelectedSession.Name + ".png"; // set ImagePath - works - Technically it does not change. I kept it if I needed later to add anything to the file name like GUID or whatever
ImageIO.RotateAndSaveImage(SelectedSession.ImagePath, (WriteableBitmap)frameSource, -270); // save new image - works
SessionImage = SelectedSession.LoadImageFromFile(); // binded the image to display
}
}
Binding in Xaml:
<Image x:Name="currentSessionImage" Source="{Binding SessionImage}"/>
In "Session.cs" class:
public BitmapImage LoadImageFromFile()
{
if (File.Exists(ImagePath)) // image path is correct
{
try
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(ImagePath);
image.EndInit();
return image;
}
catch
{
return null;
}
}
return null;
}
This has been debugged and all statements are valid. SessionImageis my property that is "binded" to an Image on my GUI. However, a very weird behavior is happening:
It is deleting the old image file and I can see that in windows explorer that the file is gone.
It is saving the new one correctly
It is loading the new image from the correct path
But then, it only displays the very First Image I ever took.
Whatever new image I send. It always displays the same image I took the first time. Could anyone please check it for me? I validated the whole code and all values are logical. No syntax errors anywhere.
Edit:
protected virtual bool SetValue<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(storage, value))
{
storage = value;
OnPropertyChanged(propertyName);
return true;
}
return false;
}
//..
protected void OnPropertyChanged(string propertyName)
{
try
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
}
}

WPF caches bitmaps that are loaded from URIs. To avoid that, load the BitmapImage directly from file instead:
using (var fileStream = new FileStream(ImagePath, FileMode.Open, FileAccess.Read))
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = fileStream;
image.EndInit();
return image;
}

Related

WPF BitmapImage loaded from Base64String not showing

I am trying to make a screen sharing application. As a first step I am trying to share the host's screen using screenshots.
In order to connect the host with the client, I am using SignalR. After the authentication is done, I am starting a timer for the host that will take a screenshot when it elapses. The screenshot code is as follows:
public static byte[] TakeScreenshot(int width = 0, int height = 0)
{
var bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height,
PixelFormat.Format32bppArgb);
// Create a graphics object from the bitmap.
var gfxScreenshot = Graphics.FromImage(bmp);
// Take the screenshot from the upper left corner to the right bottom corner.
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
0,
0,
Screen.PrimaryScreen.Bounds.Size,
CopyPixelOperation.SourceCopy);
if (width != 0 && height != 0)
bmp = new Bitmap(bmp, new Size(width, height));
using (var stream = new MemoryStream())
{
bmp.Save(stream, ImageFormat.Jpeg);
return stream.ToArray();
}
}
The timer's elapsed handler:
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var bytes = ImageHelper.TakeScreenshot();
string imageString = Convert.ToBase64String(bytes);
Communicator.Instance.Produce(new BroadcastDataComm() { DataType = DataType.Image, Data = imageString }, _connectedHost);
}
The screenshot will be received as byte array then converted to Base64String in order to broadcast it through SignalR. When the authentication is done I am creating a new WPF window (in the parent Xaml.cs class) and show it, then, all the clients authenticated to the host will receive the produced message and send the base64string to the new window:
ScreenSharingWindow _window = new ScreenSharingWindow();
private void AuthenticateSuccess(string id)
{
ConnectionId = $"Connected to: {id}";
_connectedHost = id;
Dispatcher.Invoke(() => _window.Show());
_timer.Start();
}
private void ScreenshotReceived(BroadcastDataComm ss)
{
if (!(ss.Data is string imgString))
return;
Dispatcher.Invoke(()=> _window.ImageData = imgString);
}
Finally, I am creating a Bitmap object from the string using those 2 properties in the new window and using a converter on that object so I can bind the XAML's Image control to it:
private Bitmap _image;
public Bitmap Image
{
get => _image;
set
{
_image = value;
NotifyPropertyChanged();
Image.Save(#"C:\Users\roudy\Desktop\test.jpeg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
private string _imageData;
public string ImageData
{
get => _imageData;
set
{
_imageData = value;
NotifyPropertyChanged();
var ms = new MemoryStream(Convert.FromBase64String(ImageData));
Image = new Bitmap(ms);
}
}
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Bitmap bmp)
return ImageSourceFromBitmap(bmp);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject([In] IntPtr hObject);
public BitmapSource ImageSourceFromBitmap(Bitmap bmp)
{
var handle = bmp.GetHbitmap();
try
{
var ret = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
return ret;
}
finally { DeleteObject(handle); }
}
}
I have added Image.Save(...) in the property's setter to make sure that the image is received as expected and it's working properly, and I can see the image correctly updating when I check it's thumbnail in the desktop. But for some reason, it just refuses to show in the WPF control. I've tried to create a bitmap from file and it worked fine so the binding and converter is correct.
Why won't the Image show on the control ?
Thank you.
EDIT: In order to check if the Binding is correct, I just created the bitmap from the new window's constructor and commented out the part where the parent window was setting the property. The new handler in the parent window would be:
private void ScreenshotReceived(BroadcastDataComm ss)
{
if (!(ss.Data is string imgString))
return;
//Dispatcher.Invoke(()=> _window.ImageData = imgString);
}
and the new window's constructor:
public ScreenSharingWindow()
{
InitializeComponent();
DataContext = this;
Image = new Bitmap(#"C:\Users\roudy\Desktop\SaveIcon.png");
}
Note: When I try to load the bitmap from file from within "ImageData" property it also doesn't work so it seems like it's a threading issue, when it's changed from the parent window (through SignalR handler) it doesn't show.
The problem was from SignalR. I am running 2 instances of the same project, the screenshot was being sent to the host instead of the client and the client needed it to create a new window with it, which results in the image being blank. Thank you #Clemens for making it easier to debug without needing converters and more complications.

image doesn't change - WPF

I'm trying to change an image on runtime but it's not working.
I have a user control that when you click on imagebtn it's opening a new window with a list of images.
the next step is to take the image selected from the list, close the new window, and put the image on the imagebtn.
the user control still opens in the background.
this is my code.
NewWindow:
private string myData;
public ImagesWindow()
{
InitializeComponent();
InitialImageList();
}
private async void InitialImageList()
{
//add try catch
string get = await HttpRequests.GetRequest(URLImages);
allJsonCategory = JsonConvert.DeserializeObject<List<ImageArray>>(get);
Console.WriteLine(get);
ImageBoxList.ItemsSource = images;
foreach (var item in allJsonCategory)
{
images.Add(item);
}
}
private void ImageBoxList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selectedImage = (ImageArray)ImageBoxList.SelectedItem;
myData = selectedImage.full_path;
Console.WriteLine("you clicked on: " + selectedImage.name);
ProductsCategory pro = new ProductsCategory();
pro.imagePath = myData;
this.Close();
}
my usercontrol(in mainWindow):
public void setImage(string imagePath)
{
if (!imageURL.Equals(""))
{
Image imageBtn = new Image();
var imgUrl = new Uri(imagePath);
var imageData = new WebClient().DownloadData(imgUrl);
// or you can download it Async won't block your UI
// var imageData = await new WebClient().DownloadDataTaskAsync(imgUrl);
var bitmapImage = new BitmapImage { CacheOption = BitmapCacheOption.OnLoad };
bitmapImage.BeginInit();
bitmapImage.StreamSource = new MemoryStream(imageData);
bitmapImage.EndInit();
imageBtn.Source = bitmapImage;
//this.imageBtn.InvalidateVisual();
}
}
XAML of the image:
where is my mistake?
thank you all :)

Overwrite/Update the existing image file in the folder

I'm using a webcam to capture an image and place it to picturebox and save the image to my project folder and get the image path then save to sql database. The problem is i do not know how i gonna update/overwrite the existing file
NOTE: My data is bind in datagridview when i click the cell the image will be retrieved on another form, so basically it based on the ID.
Here's what i've tried to achieve that
private void BtnOk_Click(object sender, EventArgs e)
{
if (picCapturedImage.Image != null)
{
using (var bitmap = new Bitmap(picCapturedImage.Image))
{
bitmap.Save(Common.CustomerPath, ImageFormat.Png);
_updCustomer.picCustomerImage.Image = Image.FromFile(Common.CustomerPath);
}
}
Close();
}
When i run this code it replace the image of User.Png not the capture image(Image1, Image2, etc)
User.Png is my default image when user doesn't want to browse or capture an image
Here's the code for saving the image in the project folder
I used loop to avoid overwriting the image in the project folder, so when i saved the image
Image0, Image1, Image2, etc
if (picCapturedImage.Image != null) {
using (var bitmap = new Bitmap(picCapturedImage.Image))
{
for (int i = 0; i < int.MaxValue; i++)
{
var fileName = $#"..\..\Resources\Photos\Image{i}.png";
if (!File.Exists(fileName)) {
bitmap.Save(fileName, ImageFormat.Png);
Common.CustomerPath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $#"..\..\Resources\Photos\Image{i}.png"));
_regCustomer.picCustomerImage.Image = Image.FromFile(Common.CustomerPath);
break;
}
}
}
}
Close();
Here's the code where i save the path
public class Common
{
public static string CustomerPath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"..\..\Resources", "User.png"));
}
Here's the code for retrieving the image path
_updCustomer.picCustomerImage.Image = Image.FromFile(customer.ImagePath);

How to display 2 images at same time in winform

In winforms I have two picture box one for portrait and other for Landscape.
Do to the size of the file or some reasons they are not downloading at same time,
let say if portrait image downloaded first,Now I click the updated button it was showing portrait image,
after second image downloaded when I click the update button it was showing Landscape image only.
I need both images should show, after downloading both images, but in my case it showing only one image(the latest downloaded image).
what should I do, Here is the code.
private void DisplayLogos(LogoHeader imageHeader)
{
if (imageHeader.carId == 2)
{
PortraitLabel.Text = "Portrait Image";
PortraitLabel.Visible = true;
MemoryStream ms = new MemoryStream(imageHeader.Images.First());
Image image = Image.FromStream(ms);
Bitmap bmp = new Bitmap(image);
PortraitPictureBox.Image = image;
PortraitPictureBox.Visible = true;
}
else if (imageHeader.carId == 1)
{
LandscapeLabel.Text = "Landscape Image ";
LandscapeLabel.Visible = true;
MemoryStream ms = new MemoryStream(imageHeader.Images.First());
LandscapePictureBox.Image = Image.FromStream(ms);
LandscapePictureBox.Visible = true;
}
}
public class LogoHeader
{
public LogoHeader(Access au, int Id)
{
carId = Id;
}
public int carId { get; set; }
public byte[] image{ get; set; }
public List<byte[]> Images
{
get
{
List<byte[]> logos = new List<byte[]>();
logos.Add(image);
return logos;
}
}
}
In order to keep the image you can't close (or change) the stream, you can simply create another one.
private void DisplayLogos(LogoHeader imageHeader)
{
if (imageHeader.carId == 2)
{
PortraitLabel.Text = "Portrait Image";
PortraitLabel.Visible = true;
MemoryStream ms1 = new MemoryStream(imageHeader.Images.First());
Image image = Image.FromStream(ms1);
Bitmap bmp = new Bitmap(image);
PortraitPictureBox.Image = image;
PortraitPictureBox.Visible = true;
}
else if (imageHeader.carId == 1)
{
LandscapeLabel.Text = "Landscape Image ";
LandscapeLabel.Visible = true;
MemoryStream ms2 = new MemoryStream(imageHeader.Images.First());
LandscapePictureBox.Image = Image.FromStream(ms2);
LandscapePictureBox.Visible = true;
}
}

Out of Memory Exception when loading large number of images through a file picker

Windows Phone 8.1:
foreach (StorageFile file in ImageFiles)
{
ClassForFolderImages CFFI = new ClassForFolderImages();
using(var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
var bitmapImage = new Windows.UI.Xaml.Media.Imaging.BitmapImage();
bitmapImage.DecodePixelWidth = 80;
bitmapImage.DecodePixelHeight = 80;
await bitmapImage.SetSourceAsync(stream);
CFFI.imageForFolder = bitmapImage;
GridViewName.Add(CFFI);
}
}
in the above snippet "ImageFiles" contains a large number of Images and "GridViewName" is the name of the observable collection which I later bind to a grid view where I wish to display these Images.
The ClassForFolderImages Class is as below:
public class ClassForFolderImages : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private BitmapImage img;
public BitmapImage imageForFolder
{
get { return img; }
set
{
img = value;
FirePropertyChangedEvent("imageForFolder");
}
}
public ClassForFolderImages()
{
}
private void FirePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
This Snippet works fine in the phone simulator and for small number of images on the device ,but on the device - as the number of images increase at some point it starts throwing the below error (it breaks at App.g.i.cs) :
Exception = The function evaluation was disabled because of an out of memory exception.
Handled = The function evaluation was disabled because of an out of memory exception.
Message = The function evaluation was disabled because of an out of memory exception.
Can anyone suggest changes to overcome this problem?

Categories