I have the following XAML code :
<Image x:Name="armImage" Source="{Binding PlayerImage}">
</Image>
Here's what's happenning behind the scenes.I have this property :
private BitmapImage playerImage;
public BitmapImage PlayerImage
{
get { return playerImage; }
set
{
this.playerImage = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("PlayerImage"));
}
}
I set it like this :
private void GameStarted(object sender, EventArgs.GameStartedEventArgs e)
{
if (e.IsUIHidden)
{
MainPlayer = new Player(0, new Uri("/Images/arm.bmp", UriKind.Relative));
this.PlayerImage = DisplayImage(MainPlayer.ImageUri);
}
}
Where the DisplayImage method looks like this :
private BitmapImage DisplayImage(Uri imageUri)
{
if (imageUri != null)
{
return new BitmapImage(imageUri);
}
else
{
throw new InvalidOperationException();
}
}
The issue is the following - the image in the UI doesn't change when i set the property PlayerImage.I tried doing it without the MVVM pattern and the Uri works and the image is displayed,but when I try to do it that way it doesn't work?
Your property name is PlayerImage not PlayerImageUri change it in PropertyChanged call
this.PropertyChanged(this, new PropertyChangedEventArgs("PlayerImageUri"));
should be
this.PropertyChanged(this, new PropertyChangedEventArgs("PlayerImage"));
Related
The following is part of my View in which I have bound an Image to a property in my ViewModel:
<Image Source="{Binding Image}" Grid.Column="2" Grid.ColumnSpan="2"/>
My ViewModel is this:
public class MainWindowViewModel : INotifyPropertyChanged
{
public BitmapImage Image
{
get { return _image; }
set
{
_image = value;
OnPropertyChanged();
}
}
Action _makeScannerAlwaysOnAction;
private BitmapImage _image;
public MainWindowViewModel()
{
AddNewPersonCommand = new RelayCommand(OpenFrmAddNewPerson);
FingerPrintScannerDevice.FingerPrintScanner.Init();
MakeScannerAlwaysOn(null);
}
private void MakeScannerAlwaysOn(object obj)
{
_makeScannerAlwaysOnAction = MakeScannerOn;
_makeScannerAlwaysOnAction.BeginInvoke(Callback, null);
}
private void Callback(IAsyncResult ar)
{
FingerPrintScannerDevice.FingerPrintScanner.UnInit();
var objFingerPrintVerifier = new FingerPrintVerifier();
objFingerPrintVerifier.StartVerifingProcess();
var ms = new MemoryStream();
ms.Position = 0;
objFingerPrintVerifier.MatchPerson.Picture.Save(ms, ImageFormat.Png);
var bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
Thread.Sleep(2000);
Dispatcher.CurrentDispatcher.Invoke(() => Image = bi);
//Image = bi;
_makeScannerAlwaysOnAction.BeginInvoke(Callback, null);
}
private void MakeScannerOn()
{
while (true)
{
if (FingerPrintScannerDevice.FingerPrintScanner.ScannerManager.Scanners[0].IsFingerOn)
{
return;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
My Problem:
The problem is when I want to bind the Image it gives me the error
Must create DependencySource on same Thread as the DependencyObject
I have googled a lot and I have seen the post in SO but neither of them worked for me.
any kind of help would be very appreciated.
BitmapImage is DependencyObject so it does matter on which thread it has been created because you cannot access DependencyProperty of an object created on another thread unless it's a Freezable object and you can Freeze it.
Makes the current object unmodifiable and sets its IsFrozen property to true.
What you need to do is call Freeze before you update Image:
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
bi.Freeze();
Dispatcher.CurrentDispatcher.Invoke(() => Image = bi);
as pointed out by #AwkwardCoder here is Freezable Objects Overview
While bi.Freeze(); worked for me in one case, I've seen no difference from adding/removing
Dispatcher.CurrentDispatcher.Invoke(() => Image = bi);
The second time I used DataTemplate in xaml and all the same classes as in the first case, but I kept getting the same Error.
This was a thing that helped:
Application.Current.Dispatcher.Invoke(() => Image = bi);
Maybe accepted answer can be improved because Dispatcher.CurrentDispatcher don't actually give you UI thread.
I have an Image element that's bound to an ImageSource element inside a class that I've created. The ImageSource gets updated every time a slider is changed. When I first instantiate my window, the ImageSource is blank until the user loads a file. Once the file is loaded, the image appears and the user can scroll the slider and see the image change. They can then select "OK" on the dialog to save this pattern. This all works fine.
However, if they double-click on the item in the ListView then it will re-open this dialog to make further edits. So, it creates a new dialog and then reloads the pertinent info about the image. However, for whatever reason... the image binding no longer works. I can put a breakpoint on the ImageSource getter and everytime I change the slider, the image does get updated... However, it just doesn't appear the be binding correctly. Why would it bind correctly on the first time the window is opened, but not on subsequent openings. I'll try to lay out my code.
In my .XAML code:
<UserControl x:Class="MyControls.CreatePattern"
x:Name="PatternCreation"
...
d:DesignHeight="160" d:DesignWidth="350">
<Slider Value="{Binding ElementName=PatternCreation, Path=Pattern.ZNorm, Mode=TwoWay}" Maximum="1" Name="Slider" VerticalAlignment="Stretch" />
<Image Name="PatternPreview" Source="{Binding ElementName=PatternCreation, Path=Pattern.WPFSlice}" Stretch="Uniform"></Image>
</UserControl
In my code behind I define the Pattern to be bound:
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; }
}
In my PatternVoxelBased class, I have a WPFSlice and ZNorm properties defined like this:
protected ImageSource mWPFSlice;
public ImageSource WPFSlice
{
get { return mWPFSlice; }
set
{
mWPFSlice = value;
NotifyPropertyChanged("WPFSlice");
}
}
protected double mZNorm = 0.5;
public double ZNorm
{
get { return mZNorm; }
set
{
if (mZNorm == value) return;
mZNorm = value;
NotifyPropertyChanged("ZNorm");
WPFSlice = BuildImageAtZ(mZNorm);
}
}
I have an event to load the dialog window the first time:
private void CreatePattern_Click(object sender, RoutedEventArgs e)
{
CCreateVoxelPattern dlg = new CCreateVoxelPattern();
dlg.DataContext = DataContext;
dlg.CShow(PatternLibraryMenu);
}
My ListView Double-Click function to reload the dialog window:
private void ListViewPatternLibrary_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
PatternVoxelBased item = ((ListView)sender).SelectedValue as PatternVoxelBased;
CCreateVoxelPattern dlg = new CCreateVoxelPattern();
dlg.DataContext = DataContext;
dlg.Main.Pattern = item;
dlg.Main.LoadPattern();
dlg.CShow(PatternLibraryMenu);
}
public void LoadPattern()
{
if (Pattern == null) return;
Pattern.WPFSlice = Pattern.BuildImageAtZ(Pattern.ZNorm);
}
In your class where this is
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; }
}
you have to implement INotifyPropertyChanged.
Example
public class YourClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
protected PatternVoxelBased mPattern = new PatternVoxelBased();
public PatternVoxelBased Pattern
{
get { return mPattern ; }
set { mPattern = value; OnPropertyChanged(new PropertyChangedEventArgs("Pattern"));}
}
}
EDIT
In your Pattern-class, you have to implement that too on every Property.
when i navigate to the new page where it should display the text, it appears empty
The Xaml code i have
xmlns:vm="using:Estimation"
<Page.DataContext>
<vm:PlayerClass/>
</Page.DataContext>
this is the textBlock im trying to bind the data too.
<TextBlock x:Name="PlayerOne"
Text="{Binding PlayerOneName}"
/>
The Class im binding is as follows
public class PlayerClass :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
private string name;
public string PlayerOneName { get { return this.name; }
set { this.name = value ;
NotifyPropertChanged(PlayerOneName); } }
}}
and the class im changing the content in the text box is
private void StartButton_Click(object sender, RoutedEventArgs e)
{
if (PlayerOneTextBox.Text == EnterNameText ||
PlayerTwoTextBox.Text == EnterNameText ||
PlayerThreeTextBox.Text == EnterNameText ||
PlayerFourTextBox.Text == EnterNameText)
{
MessageDialog msgBox = new MessageDialog("Please Enter All Names Before Continuing");
msgBox.ShowAsync();
}
else
{
// playerNames.PropertyChanged += new DependencyPropertyChangedEventHandler(playerNames_PropertyChanged);
this.DataContex.PlayerOneName = PlayerOneTextBox.Text;
MessageDialog msgBox = new MessageDialog(playerNames.PlayerOneName);
msgBox.ShowAsync();
playerNames.PlayerTwoName = PlayerTwoTextBox.Text;
playerNames.PlayerThreeName = PlayerTwoTextBox.Text;
playerNames.PlayerFourName = PlayerFourTextBox.Text;
Frame.Navigate(typeof(NewRoundPage));
}
}
In the constructor set the name
public PlayerClass ()
{
PlayerOneName = "Jabba De Hutt";
}
Also set a fallback value to provide an indicator of a failed binding situation:
Text="{Binding PlayerOneName, FallBack=Unknown}"
The navigate should not change the datacontext of the textbox, change the viewmodel instead
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var PlayerNames = e.Parameter as PlayerClass;
this.DataContext.PlayerOneName = PlayerNames.PlayerOneName;
}
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();
}
I'm learning WPF and, coming from Flex and AS, it seems overly complicated at times. Opinions aside, my problem is the following.
I've created a custom control, ToolBarButton which is basically an image button that is destined to be included in a custom toolbar. I've added some properties to this control and I'd like to be able to set them from the XAML. Though the property appears in AutoCompletion on the XAML side, the Set method is never fired and the property stays null. So here's the ToolBarButton Code Behind :
public static readonly DependencyProperty ImgSrcProperty = DependencyProperty.RegisterAttached("ImgSource", typeof(string), typeof(ToolBarButton));
public static readonly DependencyProperty OnClickProperty = DependencyProperty.Register("OnClick", typeof(RoutedEventHandler), typeof(ToolBarButton));
public ToolBarButton(RoutedEventHandler OnClick, string imgSrc, Map map = null, string ConfigFile = null) :
base(ConfigFile, map)
{
if (OnClick != null) SetValue(OnClickProperty, OnClick);
if (imgSrc != null) SetValue(ImgSrcProperty, imgSrc);
this.AddChild(CreateButton());
InitializeComponent();
}
public ToolBarButton() : this(null, null) { }
private Button CreateButton()
{
BitmapImage icon = new BitmapImage();
icon.BeginInit();
icon.UriSource = new Uri(ImgSource, UriKind.Relative);
icon.EndInit();
Image img = new Image();
img.Stretch = Stretch.Fill;
img.Source = icon;
Button BtnToAdd = new Button();
BtnToAdd.Width = 35;
BtnToAdd.Height = 35;
BtnToAdd.Content = img;
BtnToAdd.Background = new ImageBrush(icon);
BtnToAdd.Click += OnClick;
return BtnToAdd;
}
public string ImgSource
{
get { return (string)GetValue(ImgSrcProperty); }
set { SetValue(ImgSrcProperty, value); }
}
public RoutedEventHandler OnClick
{
get { return (RoutedEventHandler)GetValue(OnClickProperty); }
set { SetValue(OnClickProperty, value); }
}
You'll notice two constructors, one to create the control at runtime, the other to create it from XAML.
And here's the XAML code that uses the custom control but doesn't fire the set method :
<BaseControls:ToolBar
x:Class="Basic_Mapping.Widgets.NavigationToolBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:BaseControls="clr-namespace:Basic_Mapping.Base_Controls"
mc:Ignorable="d" >
<BaseControls:ToolBarButton Width="35" Height="35" ImgSource="Assets/i_zoomin.png" ConfigFileName="ZoomIn.xml" />
Any help would be appreciated!
Ggilmann
EDIT :
Here's the Base Class used for the ToolBarButton, it also has the same problem with it's properties.
public partial class ConfigurableUserControl : UserControl
{
private XmlDocument configXML;
public static readonly DependencyProperty XmlProperty = DependencyProperty.Register("ConfigFileName", typeof(string), typeof(ConfigurableUserControl));
public static readonly DependencyProperty MapProperty = DependencyProperty.Register("Map", typeof(Map), typeof(ConfigurableUserControl));
public ConfigurableUserControl(string configFile, Map map)
{
if (configFile != null) SetValue(XmlProperty, configFile);
if (map != null) SetValue(MapProperty, map);
string file = (string)GetValue(XmlProperty);
if (file != null)
{
configXML = new XmlDocument();
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\..\\Config\\" + configFile);
if (File.Exists(path)) configXML.Load(path);
}
}
public ConfigurableUserControl() : this(null, null) { }
public string ConfigFileName
{
//get { return (string)GetValue(XmlProperty); }
set { SetValue(XmlProperty, value); }
}
public Map Map
{
get { return (Map)GetValue(MapProperty); }
set { SetValue(MapProperty, value); }
}
public XmlDocument ConfigXML
{
get { return configXML; }
}
}
My guess is that this problem, and your problems with the base class, are due to the fact that you're not implementing INotifyPropertyChanged and making the appropriate calls.
Try putting InitializeComponent(); at the beginning of the constructor as opposed to at the end where it currently is.