87 - Alarm Clock Finished
Sorry I haven't posted in a few days, I've been pretty busy. Out of no where my ex-wife told me she needed 250 dollars for daycare by Monday, she told me this Thursday night. Anyways, I managed to finish my alarm clock program! If you want to install it, you can find the MSI here just click the download button. Also if it wasn't clear from previous posts, this will only work on window computers. Also I start my new job tomorrow, so that's pretty great. I'm also going out on Tuesday to the a nickle aracde. Looking like this is going to be a good week. Anyways, let's get into the code. Last time I posted some stuff about making a popup for alarms, well I moved that to a next window like I said I would. public AlarmWindow(TimeForAlarm alarm) { InitializeComponent(); Show(); Alarm = alarm; AlarmText.Text = alarm.AlarmText; if (alarm.AlarmHasSound) { AlarmStopped = false; AlarmSound(); } } Okay so starting with the constructor, we pass in a TimeForAlarm, which is the parent class for all the alarms. We have to call Show because when you create a new window after the program started it wont be viable by default. Then we grab the alarm to be used in the whole class, and set the text in the window to whatever the alarm.AlarmText is. After that we check if there is an alarm sound. We also set AlarmStopped to false if there is a sound. private void AlarmSound() { SoundPlayer = new MediaPlayer(); SoundPlayer.Open(new Uri(Alarm.GetAlarmSound())); SoundPlayer.Play(); SoundPlayer.MediaEnded += LoopAlarm; } So this is what is called if there is a sound attached. We set up the media player then call play on it, then use += to add an event on MediaEnded. When you use += on like an int it will add the number to the current value and save it. For events += subscribes to the event so whatever method we give it will be called when that event is triggered. private void LoopAlarm(object sender, EventArgs e) { MediaPlayer player = (MediaPlayer)sender; player.Position = TimeSpan.Zero; if (!player.Volume.Equals(0.0)) { player.Volume -= .05; player.Play(); } else { AlarmStopped = true; ((MediaPlayer)sender).MediaEnded -= LoopAlarm; } } Then we have the code for looping the alarm. Because this is ran on a different thread, that's just how events work. We can't get the MediaPlayer from the class, instead we have to get it from the sender object passed in. After setting the position to zero, which we should do before we call Play just so we aren't wasting cpu cycles doing something pointless(but it's so small that it doesn't matter), we check the volume. Notice how we compare it to 0.0 instead of 0, that's because volume is a double. If you compare a double to an int, it will case the double to the int and round it, so yeah, keep that in mind. Anyways, volume defaults to 0.5 so we lower the volume by .05 every time, which means it plays 10 times, getting 10% softer every time. If the volume is 0, we set AlarmStopped to true and unsubscribe the event. I don't think we even need to unsubscribe the event cause it's not going to be called anymore, but like most things I do, whatever. private void CloseRight(object sender, CancelEventArgs e) { if (Alarm.AlarmHasSound && AlarmStopped == false) SoundPlayer.Stop(); } Okay so just here if the alarm has a sound, and the alarm hasn't been stopped, we call stop on the media player. public void AlarmWentOff() { Triggered = true; var timer = new Timer(); timer.Interval = 60000; timer.Elapsed += ResetTrigger; timer.Start(); App.Current.Dispatcher.Invoke(() => { new AlarmWindow(this); }); } Okay so this is where we create the new alarm window. I've already showed this code so just focus on the last line. Because AlarmWindow is a window it needs access to UI stuff. So we can't call it directly, we have to call it from the dispatcher. That will place it on the UI thread so it works correctly. If you tried to call new AlarmWindow(this) without invoking it on a dispatcher, it would just break. private static string FilePath = "Alarms.txt"; public static void Save(List alarms) { var temp = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + FilePath; FileStream outFile = File.Create(temp); XmlSerializer formatter = new XmlSerializer(alarms.GetType()); formatter.Serialize(outFile, alarms); } public static List Load() { var alarms = new List(); XmlSerializer formatter = new XmlSerializer(alarms.GetType()); var temp = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + FilePath; using (FileStream fileStream = new FileStream(temp, FileMode.OpenOrCreate)) { if (fileStream.Length != 0) { alarms = (List)formatter.Deserialize(stream); } } return alarms; } Okay last bit of code I want to show for today, the saving and loading of the alarms. For this we are using the built in XmlSerializer. This serializer works by outputing things as xml. Due to how the xml serializer works not everything can be serialized, some objects and collections don't work. There are ways around that, but for this we didn't have to do any of that. Okay, so for save we need to get the file location. For that we get the appData folder and add on the file path, in this case Alarm.txt. Then we create a file stream using that path. Next we need to create a new XmlSerializer and pass it the type of the alarm. Then we just call Serialize. For loading we do something pretty similar. We need the type of the alarm and the file path again. But this time we are reading the file stream we make instead of writing to it. No you can see the first main difference, first we are using a using statement, second we are doing FileMode.OpenOrCreate. Because we are doing Open that means file stream will hold onto the file, unlike in save we were are just calling create. Now we don't want it holding onto the file, or else we wont be able to save it. A using statement makes it so after the using statement the object is disposed off. In this case that means closing the stream. In the using statement we first check if the length is 0. We don't want to bother reading it in if the file is empty. Then we just pass in the file stream to the formatters deserialize method and set that to the alarm list. After that we just have to return it. One more thing to note with the XmlSerializer. It only works on public variables, there were some variables I had that were private that needed to be saved. Now I could of changed a bunch of things around to make it work, but instead of that, I just made those variables public instead of private and added "_" to the start of the name. I think if I was working on a bigger project, I would go though that effort, but for this, meh. It can make sense to break a coding standard or practice from time to time. My thoughts on standards and practices is that they are only okay to break when you understand why they are in place. Alright so that's all for today! If you running windows, please try out my little program. Let me know if anything breaks. I wont be doing anything Tuesday because of the date I mentioned, sorry. Guess even I have a life from time to time. Also I might be going out of state for a week for my job next week, if so, no content, sorry again. But soon, there will be more regular content!
4/29/2018 6:53:42 PM

Add Comment Auther