So i'm trying to loop through a folder and change the image source each 2 seconds.
I think my code is right, but I seem to be missing something since my image won't update, but I don't get an error.
The code populates my array of files so it finds the pictures, I'm just doing something wrong to set the image source.
XAML code
<Grid>
<Image x:Name="Picture" Source="{Binding ImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
C# code
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource {
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Start();
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
_MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
Anyone know what I'm doing wrong ?
Updated code
XAML
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
C#
public partial class IntroScreen : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string[] files;
private System.Timers.Timer timer;
private int counter;
private int Imagecounter;
Uri _MainImageSource = null;
public Uri MainImageSource
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource");
}
}
public IntroScreen()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(this.MainWindow_Loaded);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
files = Directory.GetFiles("../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
counter = 0;
timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(timer_Tick);
timer.Interval = (2000);
timer.Enabled = true;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
counter++;
MainImageSource = new Uri(files[counter - 1], UriKind.Relative);
if (counter == Imagecounter)
{
counter = 0;
}
}
I'm not getting any error's but the image still isen't switching. I'm wondering if my paths are even working. Is there any way to test this ?
You have forgot to do notify the update to MainImageSource to the binding.
To do so, you have to implement the interface : INotifyPropertyChanged and define DataContext.
And, as written in the MSDN documentation "Setting Enabled to true is the same as calling Start, while setting Enabled to false is the same as calling Stop.".
Like this:
public partial class IntroScreen : Window, INotifyPropertyChanged
{
private string[] files;
private Timer timer;
private int counter;
private int Imagecounter;
BitmapImage _MainImageSource = null;
public BitmapImage MainImageSource // Using Uri in the binding was no possible because the Source property of an Image is of type ImageSource. (Yes it is possible to write directly the path in the XAML to define the source, but it is a feature of XAML (called a TypeConverter), not WPF)
{
get
{
return _MainImageSource;
}
set
{
_MainImageSource = value;
OnPropertyChanged("MainImageSource"); // Don't forget this line to notify WPF the value has changed.
}
}
public IntroScreen()
{
InitializeComponent();
DataContext = this; // The DataContext allow WPF to know the initial object the binding is applied on. Here, in the Binding, you have written "Path=MainImageSource", OK, the "MainImageSource" of which object? Of the object defined by the DataContext.
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
setupPics();
}
private void setupPics()
{
timer = new Timer();
timer.Elapsed += timer_Tick;
timer.Interval = 2000;
// Initialize "files", "Imagecounter", "counter" before starting the timer because the timer is not working in the same thread and it accesses these fields.
files = Directory.GetFiles(#"../../Resources/Taken/", "*.jpg", SearchOption.TopDirectoryOnly);
Imagecounter = files.Length;
MessageBox.Show(Imagecounter.ToString());
counter = 0;
timer.Start(); // timer.Start() and timer.Enabled are equivalent, only one is necessary
}
private void timer_Tick(object sender, EventArgs e)
{
// WPF requires all the function that modify (or even read sometimes) the visual interface to be called in a WPF dedicated thread.
// IntroScreen() and MainWindow_Loaded(...) are executed by this thread
// But, as I have said before, the Tick event of the Timer is called in another thread (a thread from the thread pool), then you can't directly modify the MainImageSource in this thread
// Why? Because a modification of its value calls OnPropertyChanged that raise the event PropertyChanged that will try to update the Binding (that is directly linked with WPF)
Dispatcher.Invoke(new Action(() => // Call a special portion of your code from the WPF thread (called dispatcher)
{
// Now that I have changed the type of MainImageSource, we have to load the bitmap ourselves.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(files[counter], UriKind.Relative);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad; // Don't know why. Found here (http://stackoverflow.com/questions/569561/dynamic-loading-of-images-in-wpf)
bitmapImage.EndInit();
MainImageSource = bitmapImage; // Set the property (because if you set the field "_MainImageSource", there will be no call to OnPropertyChanged("MainImageSource"), then, no update of the binding.
}));
if (++counter == Imagecounter)
counter = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And your XAML does not refer to the correct property:
<Grid>
<Image x:Name="Picture" Source="{Binding MainImageSource}" Width="980" Height="760" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="350,50,0,0"></Image>
<Grid>
Why do you need to implement INotifyPropertyChanged?
Basically, when you define a binding, WPF will check if the class that contains the corresponding property defines INotifyPropertyChanged. If so, it will subscribe to the event PropertyChanged of the class.
I'm not seeing any use of the INotifyPropertyChanged interface, which would be required to update a UI item the way you are using it. As it is now, the UI control has no way of knowing that the value was updated.
Related
I have to rotate between URLs(lets say 10 urls). Every url has its own Webview and each webview is shown for 15 secs(one at a time). I can change the urls from the server and that immediately shows onto the UWP application.
If the internet is out, the WebViews should still rotate between all the urls after the interval that is why we are using multiple webviews.
Currently, the situation is, that the more URLs I change, the more RAM it takes and eventually hangs.
WebViews is heavyweight control, please don't create multiple instance to render html page, for the scenario, you could use timer to change one webview's source with mvvm pattern for each 15s. Even if the internet is out, it will still work. Please check the following code.
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public MainPage()
{
this.InitializeComponent();
initUri();
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(15);
timer.Tick += Timer_Tick;
timer.Start();
Source = new Uri("xxxxxx");
}
private List<Uri> _uris = new List<Uri>();
private void initUri()
{
_uris.Add(new Uri("xxxxxx"));
_uris.Add(new Uri("xxxxxx"));
_uris.Add(new Uri("xxxxxx"));
}
int count = 0;
private void Timer_Tick(object sender, object e)
{
Source = _uris[count];
count++;
if (count == _uris.Count)
{
count = 0;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private Uri _source;
public Uri Source
{
get
{
return _source;
}
set
{
_source = value;
OnPropertyChanged();
}
}
}
Xaml
<WebView
x:Name="MyWebView"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{x:Bind Source, Mode=OneWay}"
/>
In my Main() WPF program I run a time consuming method asynchronously. When this method is running, I fire up a secondary window that contains a ProgressBar, which I update using IProgress.
Following is an example of my setup.
MAIN Program:
public partial class MainWindow : Window
{
private ProgressBarWindow pbwWindow = null;
public MainWindow()
{
InitializeComponent();
}
private void RunMethodAsync(IProgress<int> progress)
{
Dispatcher.Invoke(() =>
{
pbwWindow = new ProgressBarWindow("Processing...");
pbwWindow.Owner = this;
pbwWindow.Show();
});
TimeConsumingMethod(progress);
}
private void TimeConsumingMethod(IProgress<int> progress)
{
for (int i = 1; i <= 100; i++)
{
// Thread.Sleep() represents actual time consuming work being done.
Thread.Sleep(100);
progress.Report(i);
}
}
private async void btnRun_Click(object sender, RoutedEventArgs e)
{
IProgress<int> progress;
progress = new Progress<int>(i => pbwWindow.SetProgressUpdate(i));
await Task.Run(() => RunMethodAsync(progress));
}
}
My ProgressBarWindow which contains the progress bar looks like this:
public partial class ProgressBarWindow : Window
{
Stopwatch stopwatch = new Stopwatch();
BackgroundWorker worker = new BackgroundWorker();
public string ElapsedTimeString { get; set; }
public ProgressBarWindow(string infoText)
{
InitializeComponent();
SetTimer();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartTimer();
}
private void SetTimer()
{
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += (s, e) =>
{
while (!worker.CancellationPending)
{
worker.ReportProgress(0, stopwatch.Elapsed);
Thread.Sleep(1000);
}
};
worker.ProgressChanged += (s, e) =>
{
TimeSpan elapsedTime = (TimeSpan)e.UserState;
ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
};
}
private void StartTimer()
{
stopwatch.Start();
worker.RunWorkerAsync();
}
private void StopTimer()
{
stopwatch.Stop();
worker.CancelAsync();
}
public void SetProgressUpdate(int progress)
{
pbLoad.Value = progress;
if (progress >= 100)
{
StopTimer();
Close();
}
}
}
I borrowed the StopWatch logic from this SO answer.
Then, on my ProgressBarWindow I have a TextBlock which I've used Binding as follows, just as the answer above says.
<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString}"/>
Now when I run the program, the method executes, and the progress bar updates just fine. However, my TextBlock that's supposed to update with the elapsed time does not get updated.
To verify my timer's running fine, I updated TextBlock value directly as follows instead of Binding and it worked as expected and displayed Elapsed Time:
worker.ProgressChanged += (s, e) =>
{
TimeSpan elapsedTime = (TimeSpan)e.UserState;
ElapsedTimeString = string.Format("{0}:{1}:{2}", elapsedTime.Minutes, elapsedTime.Seconds, elapsedTime.Milliseconds);
tbElapsedTime.Text = ElapsedTimeString;
};
So I'm guessing my problem is with the Binding and possibly using BackgroundWorker on a windows that's already being run asynchronously? How could I fix this so I could use DataBinding?
As mentioned by Ginger Ninja, you have to implement INotifyPropertyChanged and use RelativeSource={RelativeSource Self} (as additional setting to the binding):
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _ElapsedTimeString;
public string ElapsedTimeString
{
get { return _ElapsedTimeString; }
set
{
if (_ElapsedTimeString != value)
{
_ElapsedTimeString = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ElapsedTimeString"));
}
}
}
// ....
}
and the XAML:
<TextBlock Name="tbElapsedTime" Text="{Binding ElapsedTimeString, RelativeSource={RelativeSource Self}}"/>
Data binding is often used in combination with MVVM. That is IMHO the prefered way to solve your problem... If you want to use MVVM, you have to implement a view model that contains all the logic and implements INotifyPropertyChanged. Than you can simply bind properties from the view model to the view. That ensures a nice separation between (GUI related) logic and view.
I'm trying to update a BitmapImage in the UI from a BackgroundWorker thread. I know enough about background workers to generally set them up, and how to use an ObservableCollection to update a list from a BackgroundWorker, but I'm struggling getting the image to update.
When I set
So far it looks like this:
XAML:
<Image Source="{Binding ImageSource}" />
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private BitmapImage ImageSource_;
public BitmapImage ImageSource
{
get { return ImageSource_; }
set { ImageSource_= value; NotifyPropertyChanged("ImageSource"); }
}
private BackgroundWorker UpdateImageBGW = new BackgroundWorker();
public ViewModel()
{
// this works fine
ImageSource = UpdateImage();
UpdateImageBGW.DoWork += new DoWorkEventHandler(UpdateImage_DoWork);
UpdateImageBGW.RunWorkerAsync();
}
private void UpdateImage_DoWork(object sender, DoWorkEventArgs e)
{
// this gets called fine and grabs the updated image, but setting it to
// ImageSource never updates the UI
ImageSource = UpdateImage();
}
}
The problem is you are trying to update a UI element from a background thread. You cannot interact with elements created on the UI thread from any other thread because of security reasons. If you want to update the UI from a background thread, do something like this:
Dispatcher.Invoke((Action)delegate() { /*update UI thread here*/ });
This method will create the bridge that allows you to talk to the UI thread. Check out this stackoverflow thread that has more example.
Best of Luck
use ObservableCollection like this:
public partial class MainWindow : Window
{
private ObservableCollection<int> myVar;
public ObservableCollection<int> MyProperty
{
get { return myVar; }
set { myVar = value; }
}
BackgroundWorker bw;
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
MyProperty = new ObservableCollection<int>();
bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerAsync();
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i < 10;i++)
{
MyProperty.Add(i);
}
}
}
and xaml:
<ListBox HorizontalAlignment="Left" ItemsSource="{Binding MyProperty}" Height="224" Margin="93,50,0,0" VerticalAlignment="Top" Width="321"/>
I am writing an audio app on Windows Phone 8. I've created a MediaElement and a seek-bar(slider):
<MediaElement x:Name="player" CurrentStateChanged="GetTrackDuration" />
<Slider x:Name="playerSeekBar" Value="{Binding ElementName=player, Path=Position,
Mode=TwoWay, Converter={StaticResource PositionConverter}}" SmallChange="1" LargeChange="1"/>
And this is my converter code:
public class PositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double position = 0;
TimeSpan timespan = TimeSpan.Parse(value.ToString());
position = timespan.TotalSeconds;
return position;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return TimeSpan.FromSeconds((double)value);
}
}
And here is the CurrentStateChanged event code:
private void GetTrackDuration(object sender, RoutedEventArgs e)
{
var player = (MediaElement)sender;
if (player.CurrentState == System.Windows.Media.MediaElementState.Playing)
playerSeekBar.Maximum = player.NaturalDuration.TimeSpan.TotalSeconds;
}
It all seems to work OK, however there is one problem with the binding to slider - it doesn't update until I click somewhere inside the app - I mean i may click on a button that isn't connected with slider or media element or on a empty space. After I click the slider is being updated and everything works nice. BTW, the music plays normally - even at the beginning when the slider is not being updated. I tried to look on the Internet, however I am not sure what to ask, that's why I am asking You for help. If someone just knows where I could search for the solution, I would be very grateful!:)
Thank You for Your help in advance!
It looks like a problem when Slider gets Focus, I've tried to find a solution with redirecting Focus, but so far - I haven't found it. Instead I've a diffrent proposal and few remarks to your code:
Get track duration when Media is Opened not when PlayState changes:
private void player_MediaOpened(object sender, RoutedEventArgs e)
{
playerSeekBar.Maximum = (sender as MediaElement).NaturalDuration.TimeSpan.TotalSeconds;
}
Your Slider will probably be updated every little change of MediaElement's position. I think it isn't needed - it can be updated for example every second. So my proposal is - bind your Slider to a property, and notify PropertyChanged every second (use DispatcherTimer):
// In this case we need INotifyPropertyChanged -
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
// implementing interface
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string property = null)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
// Property for Binding
public double SlideValue
{
get { return player.Position.TotalSeconds; }
set { player.Position = TimeSpan.FromSeconds(value); }
}
DispatcherTimer timer = new DispatcherTimer(); // timer
// Get the duration when Media File is opened
private void player_MediaOpened(object sender, RoutedEventArgs e)
{
playerSeekBar.Maximum = (sender as MediaElement).NaturalDuration.TimeSpan.TotalSeconds;
}
public MainPage()
{
InitializeComponent();
this.DataContext = this; // Set the DataContext
Play.Click += Play_Click; // My play method
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (s, e) => { RaiseProperty("SlideValue"); };
}
private void Play_Click(object sender, RoutedEventArgs e)
{
player.AutoPlay = true;
player.Source = new Uri("music.mp3", UriKind.RelativeOrAbsolute);
timer.Start(); // DON'T forget to start the timer.
}
In this case you no longer need Converters, and your XAML code can look like this:
<MediaElement x:Name="player" MediaOpened="player_MediaOpened"/>
<Slider x:Name="playerSeekBar" Value="{Binding SlideValue, Mode=TwoWay}" SmallChange="1" LargeChange="1"/>
Above code probably still needs some improvements, but works quite fine.
EDIT - method without DataBinding and INotifyPropertyChanged
You can also accomplish your task simpler way without Binding, just using Timer and LostMouseCapture:
public partial class MainPage : PhoneApplicationPage
{
private double totalSeconds = 1;
DispatcherTimer timer = new DispatcherTimer();
private void player_MediaOpened(object sender, RoutedEventArgs e)
{
totalSeconds = (sender as MediaElement).NaturalDuration.TimeSpan.TotalSeconds;
}
public MainPage()
{
InitializeComponent();
Play.Click += Play_Click;
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (s, e) => { playerSeekBar.Value += (double)(1 / totalSeconds); };
playerSeekBar.LostMouseCapture += (s, e) =>
{ player.Position = TimeSpan.FromSeconds(playerSeekBar.Value * totalSeconds); };
}
private void Play_Click(object sender, RoutedEventArgs e)
{
player.AutoPlay = true;
player.Source = new Uri("music.mp3", UriKind.RelativeOrAbsolute);
timer.Start(); // DON'T forget to start the timer.
}
}
In XAML:
<MediaElement x:Name="player" MediaOpened="player_MediaOpened"/>
<Slider x:Name="playerSeekBar" Value="0" SmallChange="0.01" Maximum="1.0"/>
Hope this helps.
I have these :
A Form with a Label on it
A class "Business"
A class "TimerHelper"
I'd like when I update the property MyTime, update the textbox too
My "Business" class look like this :
public class MyBusinessClass : INotifyPropertyChanged
{
public void MakeSound(object sender, ElapsedEventArgs e)
{
// I change MyTime here
}
public event PropertyChangedEventHandler PropertyChanged;
private int myTime;
public int MyTime
{
get { return myTime; }
set
{
myTime= value;
InvokePropertyChanged(new PropertyChangedEventArgs("MyTime"));
}
}
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
The "TimerHelper" look like this :
public class TimerHelper
{
private Timer _timer;
public void Run()
{
_timer = new Timer(1000);
_timer.Enabled = true;
_timer.Elapsed += new ElapsedEventHandler(MyBusinessClass.MakeSound);
}
}
In the forms, I tried this :
myTextBox.DataBindings.Add("Text", new MyBusinessClass(), "MyTime");
but I get an exception in the method "InvokePropertyChanged"
I tried this :
Invoke((MethodInvoker)delegate { myTextBox.Text = new MyBusinessClass().MyTime; });
but the textBox is never updated
I found the answer there :
A generic asynchronous INotifyPropertyChanged helper
I think your problem is you are trying to update the text box through databibding from other thread.
I mean, you're using System.Timers.Timer class which invokes Elapsed event in a threadpool.
Databinding fails updating the control because you're changing your business class in that threadpool.
You should change of thread context in the Elapsed event handler and update your business model in the main thread (where text box was created).
public class TimerHelper
{
private Timer _timer;
public void Run()
{
_timer = new Timer(1000);
_timer.Enabled = true;
_timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
}
}
void OnTimerElapsed (object sender, ElapsedEventArgs e)
{
if (myTextBox.InvokeRequired)
{
myTextBox.Invoke(MyBusinessClass.MakeSound);
}
}
}
Although I do not see where you are setting the value of MyTime (I assume it is in MakeSound?), you are setting the value of MyTime on a thread other than the UI thread. You are probably using the System.Timers.Timer class, which does not execute the event handler on the UI thread. If you want to use System.Timers.Timer and make changes to the UI in its ElapsedEventHandler delegate, you need to make use of the SynchronizingObject property on the timer. See this MSDN article for more information: Comparing the Timer Classes