I have two MediaElements created in my ViewModel and bound in my View. On MediaOne.MediaEnded(), I'm trying to fire another method. BUT, MediaEnded() never fires!
Here is the relevant code:
ViewModel:
private MediaElement _mediaOne;
public MediaElement MediaOne
{
get { return _mediaOne; }
set { _mediaOne = value; NotifyPropertyChanged(); }
}
private MediaElement _mediaTwo;
public MediaElement MediaTwo
{
get { return _mediaTwo; }
set { _mediaTwo = value; NotifyPropertyChanged(); }
}
public SpeechViewModel()
{
_mediaOne = new MediaElement();
_mediaTwo = new MediaElement();
MediaOne.MediaEnded += MediaOne_MediaEnded;
MediaTwo.MediaEnded += MediaTwo_MediaEnded;
}
private async void MediaOne_MediaEnded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
// Do stuff
}
private async void MediaTwo_MediaEnded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
// Do other stuff
}
XAML
<ContentControl Content="{Binding mediaOne}"/>
<ContentControl Content="{Binding mediaTwo}"/>
Related
I'm trying to learn the MVVM structure. How can I update a variable that changes constantly in another class in the UI.
I created a simple example because the project codes are too much. But I failed.
I would be very grateful if you could tell me where I went wrong. Thanks.
MyModel
public class Temperature : INotifyPropertyChanged
{
private double _memsTemperature;
private double _cpuTemperature;
private double _animalTemperature;
public double MemsTemperature
{
get { return _memsTemperature; }
set
{
_memsTemperature = value;
OnPropertyChanged("MemsTemperature");
}
}
public double CpuTemperature
{
get { return _cpuTemperature; }
set
{
_cpuTemperature = value;
OnPropertyChanged("CpuTemperature");
}
}
public double AnimalTemperature
{
get { return _animalTemperature; }
set
{
_animalTemperature = value;
OnPropertyChanged("AnimalTemperature");
}
}
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
public Temperature()
{
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
dispatcherTimer.Start();
}
private void DispatcherTimer_Tick(object sender, System.EventArgs e)
{
MemsTemperature = MemsTemperature + 1;
CpuTemperature = CpuTemperature + 2;
AnimalTemperature = AnimalTemperature + 3;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
MainWindowViewModel
public class MainWindowViewModel
{
public double MemTemp { get; set; }
public MainWindowViewModel()
{
MemTemp = new Temperature().MemsTemperature;
}
}
Main Window Xaml and C# Code
<TextBlock Text="{Binding MemTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
The MainWindowViewModel should expose a Temperature property, e.g. like this:
public class MainWindowViewModel
{
public Temperature Temperature { get; } = new Temperature();
}
and the Binding should then look like this:
<TextBlock Text="{Binding Temperature.MemsTemperature}"/>
Neither Mode=TwoWay nor UpdateSourceTrigger=PropertyChanged makes sense on the Binding of a TextBlock's Text property.
The OnPropertyChanged method would simpler and safer be implemented like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You have a XAML page with UI controls that bind to those constantly-changing properties. When you send out the PropertyChanged notifications, the UI control will automatically update itself.
The problem with the code you wrote is that you never bound to the actual temperature. XAML doesn't know how to translate MemTemp into anything other than it's name unless you write a DataTemplate for it.
For example, (assuming a grid) something like this:
<TextBlock Grid.Row="0" Grid.Column="0" Text="Animal: "/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MemTemp.AnimalTemperature}"/>
I would define an explicit worker class which performs the measurements. This class
has an event (OnMeasurement), which can be subscribed in the ViewModel:
// Arguments for the mesurement event (temperature, ...)
public class MeasurementEventArgs : EventArgs
{
public double Temperature { get; }
public MeasurementEventArgs(double temperature)
{
Temperature = temperature;
}
}
public class MeasurementWorker
{
private readonly CancellationTokenSource _tcs = new CancellationTokenSource();
// Provides an event we can subscribe in the view model.
public event Action<object, MeasurementEventArgs> OnMeasurement;
public void Stop()
{
_tcs.Cancel();
}
// Measurement routine. Perform a measurement every second.
public async Task Start()
{
try
{
var rnd = new Random();
while (!_tcs.IsCancellationRequested)
{
var temperature = 20 * rnd.NextDouble();
OnMeasurement?.Invoke(this, new MeasurementEventArgs(temperature));
await Task.Delay(1000, _tcs.Token);
}
}
catch (TaskCanceledException) { }
// TODO: Create an error event to catch exceptions from here.
catch { }
}
}
In your MainWindow class you instantiate your viewmodel and your worker:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(new MeasurementWorker());
}
// Register in XAML with <Window ... Closing="StopMeasurement">
public async void StopMeasurement(object sender, System.ComponentModel.CancelEventArgs e)
{
var vm = DataContext as MainWindowViewModel;
await vm.StopMeasurement();
}
}
In your view model you can subscribe to the worker event and raise OnPropertyChanged in your callback function:
public class MainWindowViewModel : INotifyPropertyChanged
{
private double _memsTemperature;
private readonly MeasurementWorker _mw;
private readonly Task _measurementWorkerTask;
public double MemsTemperature
{
get => _memsTemperature;
set
{
_memsTemperature = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MemsTemperature)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void ProcessMeasurement(object sender, MeasurementEventArgs args)
{
MemsTemperature = args.Temperature;
}
// You can call this if you want to stop your measurement. Should be called if you close your app.
public async Task StopMeasurement()
{
_mw.OnMeasurement -= ProcessMeasurement;
_mw.Stop();
// Clean shutdown
await _measurementWorkerTask;
}
public MainWindowViewModel(MeasurementWorker mw)
{
_mw = mw;
_mw.OnMeasurement += ProcessMeasurement;
_measurementWorkerTask = _mw.Start();
}
}
I'm trying to capture video from camera and display in c# WPF form. But image dont show up when i start the program. Also debugger gives me no exception it's just running. I m debugging and i see the CapturedImage property is taking the data as supposed to be. It might be they work in the different threads. But i cant figure out. So, HELP ME,
I'm binding a ImageSource type property. As you can see,
public class VideoCapturing : INotifyPropertyChanged
{
private ImageSource capturedImage;
public ImageSource CapturedImage
{
get { return capturedImage; }
set { capturedImage = value; OnPropertyChanged("CapturedImage"); }
}
Also capturing code is here,
public void run()
{
if (cap.capture == null)
{
capture = new Emgu.CV.VideoCapture(0);
CurrentFrame = new Mat();
}
capture.ImageGrabbed += VideoCapture_ImageGrabbed;
capture.Start();
}
private void VideoCapture_ImageGrabbed(object sender, EventArgs e) // runs in worker thread
{
capture.Retrieve(CurrentFrame);
CapturedImage = ImageSourceFromBitmap(CurrentFrame.ToImage<Emgu.CV.Structure.Bgr, byte>().ToBitmap());
}
} // VideoCapturing class ends.
here is the xaml part for binding,
<Grid>
<Image x:Name="img" Source="{Binding CapturedImage}"></Image>
</Grid>
This is the mainwindow.xaml.cs,
public MainWindow()
{
InitializeComponent();
VideoCapturing VideoCapture = new VideoCapturing();
this.DataContext = VideoCapture ;
VideoCapture.run();
}
There is a typo when you call OnPropertyChanged(). "CaptureImage" but it should be "CapturedImage".
public ImageSource CapturedImage
{
get { return capturedImage; }
set { capturedImage = value; OnPropertyChanged("CapturedImage"); }
}
In MainWindow you should call VideoCapture.run() instead of capture.run().
Because of VideoCapture_ImageGrabbed runs in worker thread you have set CapturedImage on UI thread by calling Dispatcher.BeginInvoke.
private void VideoCapture_ImageGrabbed(object sender, EventArgs e)
{
capture.Retrieve(CurrentFrame);
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
CapturedImage = ImageSourceFromBitmap(CurrentFrame.ToImage<Emgu.CV.Structure.Bgr, byte>().ToBitmap());
}));
}
So I planning to bind label from two files or more, because I place the label and the cs file in separate way. For example:
SettingServicesPhone.xaml
<Label x:Name="sipLoginStatus"
Width="106"
Height="27"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{Binding SipLoginStatusContent}"
FontSize="13" />
For the SettingServicePhone.xaml.cs I declared public String sipLoginStatusContent;
And I use Settings.xaml and Setting.xaml.cs as a container of all functions.
I've declared public static SettingsServicesPhone setCall = new SettingsServicesPhone(); on Setting.xaml.cs. And also write get set.
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
And here the example of onclick button that I stated on Settings.xaml.cs
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
It's work fine if I included them in one file. But seems like it doesn't running if I make it separate. Am I doing it wrong way? Thank you.
Set the DataContext of the window where the Label is defined to an instance of the class where the SipLoginStatusContent property is defined:
public partial class Settings : Window
{
public static SettingsServicesPhone setCall = new SettingsServicesPhone();
public Settings()
{
InitializeComponent();
DataContext = this; //<--
}
public String SipLoginStatusContent
{
get { return setCall.sipLoginStatusContent; }
set
{
if (setCall.sipLoginStatusContent != value)
{
setCall.sipLoginStatusContent = value;
OnPropertyChanged("SipLoginStatusContent"); // To notify when the property is changed
}
}
}
public void applyBtn_Click(object sender, RoutedEventArgs e)
{
SipLoginStatusContent = "Logging In";
}
}
I have an error "Must create DependencySource on same Thread as the DependencyObject" in my project.
My comment is used to load a file and create a list. This list is bind to a ListBox. AL was working good. But I created a Task to load (load can be long). Now I have this error. I don't understand why it occurs.
There is my code :
MainView.xaml:
<ListBox ItemsSource="{Binding Results}"
SelectedItem="{Binding SelectedItem}">
<ListBox.InputBindings>
<KeyBinding Command="{Binding RemoveCommand}"
Key="Delete"/>
</ListBox.InputBindings>
</ListBox>
<Button Grid.Row="1" Grid.Column="0"
Style="{StaticResource StyleButton}"
Command="{Binding LoadCommand}"
Content="Open result"/>
MainViewModel:
#region Fields/Properties
public ImageWithPoints SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
SelectedPointIndex = 1;
OnPropertyChanged();
OnPropertyChanged("Picture");
UpdatePoints();
}
}
public List<ImageWithPoints> Results
{
get
{
return _results;
}
set
{
_results = value;
if (value == null)
{
SelectedPointIndex = 0;
}
OnPropertyChanged();
}
}
public BitmapSource Picture
{
get
{
return SelectedItem?.Picture;
}
}
#endregion
#region Load
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
public void OnLoad()
{
StartRunning(this, null);
Task loadTask = new Task(new Action(() =>
{
Load();
Application.Current.Dispatcher.Invoke(new Action(() =>
{
StopRunning(this, null);
}));
}));
loadTask.Start();
}
public bool CanLoad()
{
return !IsRunning;
}
#endregion
#region Events
public event EventHandler OnStartRunning;
public event EventHandler OnStopRunning;
private void StartRunning(object sender, EventArgs e)
{
OnStartRunning(sender, e);
}
private void StopRunning(object sender, EventArgs e)
{
OnStopRunning(sender, e);
}
#enregion
#region Methods
public void Load()
{
// Open File
// Set to list
List<ImageWithPoints> listRes;
Results = listRes;
SelectedItem = Results[0];
}
#endregion
When I remove the line SelectedItem = Results[0]; I have no error (but application don't work has it should).
Set the SelectedItem property back on the UI thread once the Task has finished:
public void OnLoad()
{
StartRunning(this, null);
Task.Factory.StartNew(new Action(() =>
{
Load();
})).ContinueWith(task =>
{
SelectedItem = Results[0];
StopRunning(this, null);
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
You can only access a UI element on the thread on which it was originally created so if your UpdatePoints() method accesses any control you must call this method on the UI thread.
I need to trigger an event when the thumb of a slider is either dragged to a new value, or clicked to a new value with snap to point. I only want this event to happen when the value is changed is these two manners, using the mouse, so a ValueChanged event won't work.
you can try this litte trick
first you need to set UpdateSourceTrigger to Explicit
<Slider Minimum="0"
Thumb.DragStarted="Slider_DragStarted"
Thumb.DragCompleted="Slider_DragCompleted"
Maximum="{Binding YourMaxBinding, Mode=OneWay}"
Value="{Binding CurrentPosition, Mode=TwoWay, UpdateSourceTrigger=Explicit}" />
code behind
private void Slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e) {
// try to prevent updating slider position from your view model
yourViewModel.DontUpdateSliderPosition = true;
}
private void Slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e) {
BindingExpression be = ((Slider)sender).GetBindingExpression(RangeBase.ValueProperty);
if (be != null) {
be.UpdateSource();
}
yourViewModel.DontUpdateSliderPosition = false;
}
code at your view model
private bool _dontUpdateSliderPosition;
public bool DontUpdateSliderPosition {
get {
return _dontUpdateSliderPosition;
}
set {
if (Equals(value, _dontUpdateSliderPosition)) {
return;
}
_dontUpdateSliderPosition = value;
yourPropertyChangedFunc("DontUpdateSliderPosition");
}
}
private int _currentPosition;
public int CurrentPosition {
get {
return _currentPosition;
}
set {
if (Equals(value, _currentPosition)) {
return;
}
_currentPosition = value;
yourPropertyChangedFunc("CurrentPosition");
}
}
private CodeBehindFuncToChangeTheSliderPosition(){
if (!DontUpdateSliderPosition) {
CurrentPosition = theNewPosition;
}
}
hope this helps