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}"
/>
Related
I have the following code in my code behind:
public partial class MainWindow
{
private Track _movieSkipSliderTrack;
private Slider sMovieSkipSlider = null;
private Label lbTimeTooltip = null;
private MediaElement Player = null;
public VideoPlayerViewModel ViewModel
{
get { return DataContext as VideoPlayerViewModel; }
}
public MainWindow()
{
InitializeComponent();
}
private void SMovieSkipSlider_OnLoaded(object sender, RoutedEventArgs e)
{
_movieSkipSliderTrack = (Track)sMovieSkipSlider.Template.FindName("PART_Track", sMovieSkipSlider);
_movieSkipSliderTrack.Thumb.DragDelta += Thumb_DragDelta;
_movieSkipSliderTrack.Thumb.MouseEnter += Thumb_MouseEnter;
}
private void Thumb_MouseEnter(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && e.MouseDevice.Captured == null)
{
var args = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left)
{
RoutedEvent = MouseLeftButtonDownEvent
};
SetPlayerPositionToCursor();
_movieSkipSliderTrack.Thumb.RaiseEvent(args);
}
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
SetPlayerPositionToCursor();
}
private void SMovieSkipSlider_OnMouseEnter(object sender, MouseEventArgs e)
{
lbTimeTooltip.Visibility = Visibility.Visible;
lbTimeTooltip.SetLeftMargin(Mouse.GetPosition(sMovieSkipSlider).X);
}
private void SMovieSkipSlider_OnPreviewMouseMove(object sender, MouseEventArgs e)
{
double simulatedPosition = SimulateTrackPosition(e.GetPosition(sMovieSkipSlider), _movieSkipSliderTrack);
lbTimeTooltip.AddToLeftMargin(Mouse.GetPosition(sMovieSkipSlider).X - lbTimeTooltip.Margin.Left + 35);
lbTimeTooltip.Content = TimeSpan.FromSeconds(simulatedPosition);
}
private void SMovieSkipSlider_OnMouseLeave(object sender, MouseEventArgs e)
{
lbTimeTooltip.Visibility = Visibility.Hidden;
}
private void SetPlayerPositionToCursor()
{
Point mousePosition = new Point(Mouse.GetPosition(sMovieSkipSlider).X, 0);
double simulatedValue = SimulateTrackPosition(mousePosition, _movieSkipSliderTrack);
SetNewPlayerPosition(TimeSpan.FromSeconds(simulatedValue));
}
private double CalculateTrackDensity(Track track)
{
double effectivePoints = Math.Max(0, track.Maximum - track.Minimum);
double effectiveLength = track.Orientation == Orientation.Horizontal
? track.ActualWidth - track.Thumb.DesiredSize.Width
: track.ActualHeight - track.Thumb.DesiredSize.Height;
return effectivePoints / effectiveLength;
}
private double SimulateTrackPosition(Point point, Track track)
{
var simulatedPosition = (point.X - track.Thumb.DesiredSize.Width / 2) * CalculateTrackDensity(track);
return Math.Min(Math.Max(simulatedPosition, 0), sMovieSkipSlider.Maximum);
}
private void SetNewPlayerPosition(TimeSpan newPosition)
{
Player.Position = newPosition;
ViewModel.AlignTimersWithSource(Player.Position, Player);
}
}
I would like to follow the MVVM pattern and have this code moved to my ViewModel which at the moment has only few properties. I have read a lot of answer here and outside of StackOverflow on the topic, I've downloaded some github projects to check out how experienced programmers handle specific situations, but none of that seem to clear out the confusion for me. I'd like to see how can my case be refactored to follow the MVVM pattern.
Those are the extra extension methods and also the ViewModel itself:
static class Extensions
{
public static void SetLeftMargin(this FrameworkElement target, double value)
{
target.Margin = new Thickness(value, target.Margin.Top, target.Margin.Right, target.Margin.Bottom);
}
public static void AddToLeftMargin(this FrameworkElement target, double valueToAdd)
{
SetLeftMargin(target, target.Margin.Left + valueToAdd);
}
}
public class VideoPlayerViewModel : ViewModelBase
{
private TimeSpan _movieElapsedTime = default(TimeSpan);
public TimeSpan MovieElapsedTime
{
get { return _movieElapsedTime; }
set
{
if (value != _movieElapsedTime)
{
_movieElapsedTime = value;
OnPropertyChanged();
}
}
}
private TimeSpan _movieLeftTime = default(TimeSpan);
public TimeSpan MovieLeftTime
{
get { return _movieLeftTime; }
set
{
if (value != _movieLeftTime)
{
_movieLeftTime = value;
OnPropertyChanged();
}
}
}
public void AlignTimersWithSource(TimeSpan currentPosition, MediaElement media)
{
MovieLeftTime = media.NaturalDuration.TimeSpan - currentPosition;
MovieElapsedTime = currentPosition;
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
I have tried to make the code copy/paste ready as requested in the comments, all of the Controls in the View's code behind are created in the XAML, if you want to fully replicate it.
The idea is to have a property and command in your VM for every area of the UI that you'd like to update or event that needs to be handled, respectively.
Just glancing at your current code, I think you will have a much easier time (you'll be able to remove a few of your event handlers) if you hook directly into your slider's Value property and bind it (two-way) to a property on your VM. Whenever the user drags, you will be able to see when the value updates and you can handle accordingly.
As far as the "hidden" effect of your scrub bar goes, you may have a much easier time just hooking into the visual state of your slider. Here are the styles and visual states.
EDIT:
public class VideoPlayerViewModel : ViewModelBase
{
// your existing properties here, if you decide that you still need them
// this could also be long/double, if you'd like to use it with your underlying type (DateTime.TotalTicks, TimeSpan.TotalSeconds, etc.)
private uint _elapsedTime = 0; //or default(uint), whichever you prefer
public uint ElapsedTime
{
get { return _elapsedTime; }
set
{
if (_elapsedTime != value)
{
_elapsedTime = value;
//additional "time changed" logic here, if needed
//if you want to skip programmatically, all you need to do is set this property!
OnPropertyChanged();
}
}
}
private double _maxTime = 0;
public double MaxTime
{
// you get the idea, you'll be binding to the media's end time in whatever unit you're using (i.e. if I have a 120 second clip, this value would be 120 and my elapsed time would be hooked into an underlying TimeSpan.TotalSeconds)
}
}
and on your slider:
Value={Binding ElapsedTime, Mode=TwoWay}
Maximum={Binding MaxTime, Mode=OneWay} //could also be OneTime, depending on the lifecycle of the control
I recommend using Caliburn Micro.
If you use that library you can bind events like this:
<Button cal:Message.Attach="Save">
or like that
<Button cal:Message.Attach="[Event MouseEnter] = [Action Save]">
Check out their website for more advanced possibilities:
https://caliburnmicro.codeplex.com/wikipage?title=Cheat%20Sheet
I have some simple rules that I follow in XAML apps:
The ViewModel should not know about the View, so no UI related code will ever be found in the ViewModel
All UI related code is in the code behind(xaml.cs)
User controls and dependency properties are your best friends, so use them. The view should be made up of user controls, each with its own ViewModel.
Inject your dependencies through constructor injection so they can be mocked when you write unit tests
You should not have mouse handlers in your viewmodel. Those events belong to the UI and hence the view. Instead, move the bloated view code to an attached behavior. From the behavior you can optionally call into your viewmodel through interfaces. E.g.:
var vm = AssociatedObject.DataContext as IPlayerViewModel;
vm?.AlignTimersWithSource(...);
you can not use events in viewmodel. So you will have to create command pattern class and just create viewmodel class. After that can use name space of viewmodel in xml file or view file using "xmlns tag. And create resource for the class and provide meaning full key name. And set datacontext in
<Grid datacontext="nameofresource">. Now do the keybinding.
Note: If you need more clearification, reply
I am using MediaElement to show video clips in a loop for long period of time. After some time (hours for Win 7 / 4 GB RAM) the program crashes with exception of type "Insufficient memory". I have monitored the memory used while playing with Process Explorer-Sysinternals and also logged it using System.Diagnostics.Process methods. Both ways show gradually increasing of used memory.
Here is the code:
XAML:
<Grid Name="GridTest">
<MediaElement x:Name="MediaPlayer"
LoadedBehavior="Manual"
MediaEnded="VideoControl_MediaEnded"
MediaOpened="MediaPlayer_MediaOpened"
Source="{Binding Mode=OneWay,
Path=MySource}" />
</Grid>
.cs:
public partial class MainWindow : Window
{
public MainViewModel model = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.GridTest.DataContext = model;
// fill in model.MediaFilesUris:
...
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// choose the next media file
...
MediaPlayer.Play();
}
private void VideoControl_MediaEnded(object sender, RoutedEventArgs e)
{
// choose the next media file
...
model.OnPropertyChanged("MySource");
MediaPlayer.Play();
}
}
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public Uri[] MediaFilesUris = null;
public int crn = 0;
public Uri MySource { get { if (MediaFilesUris != null && MediaFilesUris.Count()>0) return MediaFilesUris[crn]; else return null; } }
}
I have also tested the case when MediaElement object is created dynamically, destroyed (together with all unsubscribing from events, etc.) after several clips and created again. Memory got consumed increasingly again.
Any suggestions would be appreciated!
Try to specify MediaElement UnloadingBehavior="Close"property in your XAML.
According to MSDN MediaState::Close indicates that
All media resources are released (including video memory).
My proposal is to make the following:
private void VideoControl_MediaEnded(object sender, RoutedEventArgs e)
{
// choose the next media file
...
//make the following explicitly
MediaPlayer.Stop();
MediaPlayer.Source = null;
model.OnPropertyChanged("MySource");
MediaPlayer.Play();
}
I have an async method. I am trying to bind listview from a json file on web.
I am developing an Universal Windows Platform App. The list view is being loaded when I open the page on second time. But off course I want list view loaded on first load. What can cause the problem for it? Thanks.
This is the codebehind of my XAML.
public MyPage()
{
this.InitializeComponent();
Namedays = new List<NamedayModel>();
LoadData();
listview1.ItemsSource = Namedays;
}
public async void LoadData()
{
Namedays = await GetAllNamedaysAsync();
}
private static List<NamedayModel> allNamedaysCache;
public static async Task<List<NamedayModel>> GetAllNamedaysAsync()
{
if (allNamedaysCache != null)
return allNamedaysCache;
var client = new HttpClient();
var stream = await client.GetStreamAsync("http://www.example.com/myfile.json");
var serializer = new DataContractJsonSerializer(typeof(List<NamedayModel>));
allNamedaysCache = (List<NamedayModel>)serializer.ReadObject(stream);
return allNamedaysCache;
}
The are couple of things you make wrong:
don't make async void if not really needed (for example events),
your are running your LoadData() in constructor as fire-forget, it doesn't await (it's also impossible in constructor), the code goes further and you set listview's itemssource to Namedays. You are not using binding for ItemsSource, thus when you change Namedays in loaded method, it's not reflected by listview.
apart from that, also by asigning new value to Namedays in LoadData(), you don't change the value of listview's itemssource - it's still pointing to old collection.
Much better in this case would be if you had used binding for ItemsSource, define property and load data asynchronously in for example Loaded event. Sample code, XAML:
<ListView ItemsSource="{Binding Namedays}"/>
And the code behind:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private List<NamedayModel> namedays = new List<NamedayModel>();
public List<NamedayModel> Namedays { get { return namedays; } set { namedays = value; RaiseProperty(nameof(Namedays)); } }
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Namedays = await GetAllNamedaysAsync();
}
public static async Task<List<NamedayModel>> GetAllNamedaysAsync()
{
if (allNamedaysCache != null)
return allNamedaysCache;
var client = new HttpClient();
var stream = await client.GetStreamAsync("http://www.tyosoft.com/namedays_hu.json");
var serializer = new DataContractJsonSerializer(typeof(List<NamedayModel>));
allNamedaysCache = (List<NamedayModel>)serializer.ReadObject(stream);
return allNamedaysCache;
}
}
As a side note - you may also think of using ObservableCollection instead of List if you want to modify it without reassigning.
Your listview1 does not know about the data so it does not load them. When you open the page for second time, data is already loaded and listview1 can display them.
You need to implement INotifyPropertyChanged interface like so:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
and then call OnPropertyChanged(nameof(Namedays)) after you load your data
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.
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.