using System; using System.Configuration; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using System.Threading.Tasks; using System.Windows.Media.Effects; using System.Text.Json; using System.Xml.Linq; namespace MultiWoWLauncher { public partial class MainWindow : Window { // Config file key for WoW path private const string WowPathKey = "WowExecutablePath"; private const string ClearCacheKey = "ClearCache"; // AppData config constants private const string AppName = "MultiWoWLauncher"; private const string ConfigFileName = "config.xml"; private string ConfigFilePath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppName, ConfigFileName); // Class level variables private DispatcherTimer _contentRefreshTimer; // Combined refresh timer private HttpClient _httpClient; private string _playerCountUrl = "http://127.0.0.1/count.php"; private bool _isStatusUpdating = false; private string _newsUrl = "http://127.0.0.1/news.php"; // Change to your news endpoint private DateTime _lastNewsUpdate = DateTime.MinValue; public MainWindow() { InitializeComponent(); // Initialize HTTP client _httpClient = new HttpClient(); _httpClient.Timeout = TimeSpan.FromSeconds(10); // Set a reasonable timeout // Load settings from config LoadSettingsFromConfig(); } private void Window_Loaded(object sender, RoutedEventArgs e) { // Load the embedded video resource LoadEmbeddedVideo(); // Initial load of content _ = UpdatePlayerCount(); // Initial player count update LoadNews(); // Initial news load // Initialize the unified content refresh InitializeContentRefresh(); } private void Window_Closed(object sender, EventArgs e) { // Clean up resources if (_contentRefreshTimer != null) { _contentRefreshTimer.Stop(); } if (_httpClient != null) { _httpClient.Dispose(); } } // Load settings from config file private void LoadSettingsFromConfig() { try { // Load Clear Cache setting string clearCacheSetting = GetSettingFromConfig(ClearCacheKey, "False"); ClearCacheCheckBox.IsChecked = Convert.ToBoolean(clearCacheSetting); } catch (Exception ex) { Console.WriteLine($"Error loading settings: {ex.Message}"); } } // Save setting to config file private void SaveSettingToConfig(string key, string value) { try { // Create the config directory if it doesn't exist string configDir = Path.GetDirectoryName(ConfigFilePath); if (!Directory.Exists(configDir)) { Directory.CreateDirectory(configDir); } // Load or create the XML configuration file XDocument doc; if (File.Exists(ConfigFilePath)) { // Load existing config doc = XDocument.Load(ConfigFilePath); } else { // Create new config file doc = new XDocument( new XDeclaration("1.0", "utf-8", "yes"), new XElement("configuration", new XElement("appSettings") ) ); } // Get or create the appSettings element XElement appSettings = doc.Root.Element("appSettings"); if (appSettings == null) { appSettings = new XElement("appSettings"); doc.Root.Add(appSettings); } // Find existing setting XElement setting = appSettings.Elements("add") .FirstOrDefault(e => (string)e.Attribute("key") == key); if (setting != null) { // Update existing setting setting.Attribute("value").Value = value; } else { // Add new setting appSettings.Add(new XElement("add", new XAttribute("key", key), new XAttribute("value", value) )); } // Save the configuration doc.Save(ConfigFilePath); } catch (Exception ex) { MessageBox.Show($"Error saving setting to configuration: {ex.Message}", "Configuration Error", MessageBoxButton.OK, MessageBoxImage.Error); } } // Get setting from config file private string GetSettingFromConfig(string key, string defaultValue = "") { try { if (File.Exists(ConfigFilePath)) { XDocument doc = XDocument.Load(ConfigFilePath); XElement appSettings = doc.Root.Element("appSettings"); if (appSettings != null) { XElement setting = appSettings.Elements("add") .FirstOrDefault(e => (string)e.Attribute("key") == key); if (setting != null) { return (string)setting.Attribute("value"); } } } } catch (Exception ex) { Console.WriteLine($"Error loading setting from configuration: {ex.Message}"); } return defaultValue; } // Initialize the unified content refresh private void InitializeContentRefresh() { // Create and configure the timer for all content _contentRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(10) // Refresh every 10 seconds }; // Set up the timer event _contentRefreshTimer.Tick += async (sender, e) => { await RefreshAllContent(); }; // Start the timer _contentRefreshTimer.Start(); // Update the last updated text immediately UpdateLastUpdatedText(); } // Method to refresh all content private async Task RefreshAllContent() { try { // Start both refresh tasks concurrently for efficiency Task newsTask = FetchNewsContent(); Task playerCountTask = UpdatePlayerCount(); // Wait for news content to be retrieved string newsContent = await newsTask; // Update the news panel UpdateNewsPanel(newsContent); // Update last refresh time for news _lastNewsUpdate = DateTime.Now; // Update the last updated text UpdateLastUpdatedText(); // Add a subtle visual indicator of refresh FlashNewsRefreshIndicator(); FlashPlayerCountIndicator(); // Wait for player count task to complete (should already be done) await playerCountTask; Console.WriteLine("All content refreshed at: " + DateTime.Now.ToString("HH:mm:ss")); } catch (Exception ex) { Console.WriteLine($"Error in content refresh: {ex.Message}"); } } // Update the last updated text private void UpdateLastUpdatedText() { try { // Get the LastUpdatedText element TextBlock lastUpdatedText = FindName("LastUpdatedText") as TextBlock; if (lastUpdatedText != null) { // Update it with the current time lastUpdatedText.Text = $"Updated: {DateTime.Now.ToString("HH:mm:ss")}"; } else { Console.WriteLine("LastUpdatedText not found in XAML"); } } catch (Exception ex) { Console.WriteLine($"Error updating last updated text: {ex.Message}"); } } private async Task UpdatePlayerCount() { // Prevent multiple simultaneous updates if (_isStatusUpdating) return; _isStatusUpdating = true; try { // Fetch player count from your local endpoint string response = await _httpClient.GetStringAsync(_playerCountUrl); // Try to parse the response as an integer if (int.TryParse(response.Trim(), out int playerCount)) { // Update the UI with the player count OnlinePlayersText.Text = $"{playerCount:N0} Players Online"; // Update the status indicator color based on player count UpdateStatusIndicator(playerCount); } else { // If we couldn't parse the response as a number OnlinePlayersText.Text = "Server Status Unknown"; SetStatusIndicatorColor(Colors.Gray); } } catch (Exception ex) { // Handle any errors in fetching or parsing the data Console.WriteLine($"Error updating player count: {ex.Message}"); OnlinePlayersText.Text = "Server Offline"; SetStatusIndicatorColor(Colors.Red); } finally { _isStatusUpdating = false; } } private void UpdateStatusIndicator(int playerCount) { // Change color based on population if (playerCount > 500) { // High population - green SetStatusIndicatorColor(Colors.LimeGreen); } else if (playerCount > 100) { // Medium population - yellow SetStatusIndicatorColor(Colors.Yellow); } else if (playerCount > 0) { // Low population - orange SetStatusIndicatorColor(Colors.Orange); } else { // No players - red SetStatusIndicatorColor(Colors.Red); } } private void SetStatusIndicatorColor(Color color) { // Update the status indicator color StatusIndicator.Fill = new SolidColorBrush(color); // Update the glow effect if (StatusIndicator.Effect is DropShadowEffect effect) { effect.Color = color; } } private void FlashPlayerCountIndicator() { // Find the player count border var playerCountBorder = GetPlayerCountBorder(); if (playerCountBorder == null) return; // Store the original background Brush originalBackground = playerCountBorder.Background; // Create a subtle animation DispatcherTimer flashTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; int flashCount = 0; flashTimer.Tick += (s, e) => { flashCount++; if (flashCount == 1) { // Flash to a slightly lighter color playerCountBorder.Background = new SolidColorBrush(Color.FromArgb(80, 0, 0, 0)); } else { // Restore original background playerCountBorder.Background = originalBackground; flashTimer.Stop(); } }; flashTimer.Start(); } private Border GetPlayerCountBorder() { // Find the player count text block var playerCountText = FindName("OnlinePlayersText") as TextBlock; if (playerCountText == null) return null; // Walk up the visual tree to find the parent Border DependencyObject current = playerCountText; while (current != null) { if (current is Border border) { return border; } current = VisualTreeHelper.GetParent(current); } return null; } private void FlashNewsRefreshIndicator() { // Find the news header border var newsHeader = FindNewsHeaderBorder(); if (newsHeader == null) return; // Store the original background Brush originalBackground = newsHeader.Background; // Create a subtle animation DispatcherTimer flashTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; int flashCount = 0; flashTimer.Tick += (s, e) => { flashCount++; if (flashCount == 1) { // Flash to a slightly lighter color newsHeader.Background = new SolidColorBrush(Color.FromRgb(50, 50, 50)); } else { // Restore original background newsHeader.Background = originalBackground; flashTimer.Stop(); } }; flashTimer.Start(); } private Border FindNewsHeaderBorder() { // Find the news panel first var newsPanel = FindName("NewsPanel") as StackPanel; if (newsPanel == null) return null; // Walk up the visual tree to find the parent Border in Row 0 DependencyObject current = newsPanel; // Look for parent elements until we find what might be the header border while (current != null) { // Check if this is a Grid if (current is Grid grid) { // Check for a Border in Row 0 foreach (UIElement child in grid.Children) { if (child is Border border && Grid.GetRow(child) == 0) { return border; } } } current = VisualTreeHelper.GetParent(current); } return null; } // Method to load news private async void LoadNews() { try { // Only update news if it hasn't been updated in the last hour if (DateTime.Now - _lastNewsUpdate > TimeSpan.FromHours(1)) { string newsContent = await FetchNewsContent(); UpdateNewsPanel(newsContent); _lastNewsUpdate = DateTime.Now; // Update the last updated text after initial load UpdateLastUpdatedText(); } } catch (Exception ex) { Console.WriteLine($"Error loading news: {ex.Message}"); // Show error in news panel var newsPanel = FindName("NewsPanel") as StackPanel; if (newsPanel != null) { newsPanel.Children.Clear(); newsPanel.Children.Add(CreateNewsErrorItem("Unable to load news updates. Please check your connection.")); } } } // Method to fetch news content private async Task FetchNewsContent() { try { // Use the existing HttpClient instead of creating a new one string content = await _httpClient.GetStringAsync(_newsUrl); return content; } catch (Exception ex) { Console.WriteLine($"Error fetching news: {ex.Message}"); // Fallback to default news if server is unavailable return GetDefaultNews(); } } // Method to parse and update the news panel private void UpdateNewsPanel(string newsContent) { var newsPanel = FindName("NewsPanel") as StackPanel; if (newsPanel == null) return; // Clear existing items newsPanel.Children.Clear(); try { // Parse the JSON content as an array JsonDocument doc = JsonDocument.Parse(newsContent); // The root element should be an array if (doc.RootElement.ValueKind == JsonValueKind.Array) { bool hasNews = false; // Process each news item in the array foreach (JsonElement element in doc.RootElement.EnumerateArray()) { // Create a NewsItem object from the JSON element NewsItem item = new NewsItem { Title = GetJsonPropertyString(element, "title", "Untitled"), Content = GetJsonPropertyString(element, "content", "No content"), Date = GetJsonPropertyString(element, "date", DateTime.Now.ToString("yyyy-MM-dd")), Type = GetJsonPropertyString(element, "type", "update") }; // Add the news item to the panel newsPanel.Children.Add(CreateNewsItem(item)); hasNews = true; } // If no news items were found if (!hasNews) { newsPanel.Children.Add(CreateDefaultNewsItem("No news updates available.")); } } else { // If the root element is not an array newsPanel.Children.Add(CreateNewsErrorItem("Invalid news format: Expected an array of news items.")); } } catch (JsonException ex) { Console.WriteLine($"Error parsing news JSON: {ex.Message}"); newsPanel.Children.Add(CreateNewsErrorItem($"Error parsing news data: {ex.Message}")); } catch (Exception ex) { Console.WriteLine($"Error processing news: {ex.Message}"); newsPanel.Children.Add(CreateNewsErrorItem($"Error loading news content: {ex.Message}")); } } // Helper method to safely get string property from JSON private string GetJsonPropertyString(JsonElement element, string propertyName, string defaultValue) { if (element.TryGetProperty(propertyName, out JsonElement property) && property.ValueKind == JsonValueKind.String) { return property.GetString() ?? defaultValue; } return defaultValue; } // Class to represent a news item private class NewsItem { public string Title { get; set; } public string Content { get; set; } public string Date { get; set; } public string Type { get; set; } // e.g., "update", "maintenance", "event" } // Create a UI element for a news item private UIElement CreateNewsItem(NewsItem item) { // Container for the entire news item Border container = new Border { Margin = new Thickness(0, 0, 0, 15), BorderThickness = new Thickness(0, 0, 0, 1), BorderBrush = new SolidColorBrush(Color.FromArgb(50, 255, 255, 255)), Padding = new Thickness(0, 0, 0, 10) }; // Content inside the news item StackPanel content = new StackPanel(); // News title TextBlock titleBlock = new TextBlock { Text = item.Title, FontWeight = FontWeights.Bold, FontSize = 14, Foreground = new SolidColorBrush(Colors.White), TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 5) }; // News content TextBlock contentBlock = new TextBlock { Text = item.Content, Foreground = new SolidColorBrush(Color.FromRgb(204, 204, 204)), TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 5) }; // News date TextBlock dateBlock = new TextBlock { Text = item.Date, FontStyle = FontStyles.Italic, FontSize = 11, Foreground = new SolidColorBrush(Color.FromRgb(153, 153, 153)), HorizontalAlignment = HorizontalAlignment.Right }; // Add color indicator based on news type Border typeIndicator = new Border { Width = 3, HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(-10, 0, 0, 0) }; // Set color based on news type if (item.Type?.ToLower() == "maintenance") { typeIndicator.Background = new SolidColorBrush(Color.FromRgb(255, 165, 0)); // Orange } else if (item.Type?.ToLower() == "update") { typeIndicator.Background = new SolidColorBrush(Color.FromRgb(0, 174, 255)); // Blue } else if (item.Type?.ToLower() == "event") { typeIndicator.Background = new SolidColorBrush(Color.FromRgb(124, 252, 0)); // Green } else { typeIndicator.Background = new SolidColorBrush(Color.FromRgb(200, 200, 200)); // Grey } // Assemble the news item content.Children.Add(titleBlock); content.Children.Add(contentBlock); content.Children.Add(dateBlock); Grid newsGrid = new Grid(); newsGrid.Children.Add(typeIndicator); newsGrid.Children.Add(content); container.Child = newsGrid; return container; } // Create a simple news item from plain text private UIElement CreateSimpleNewsItem(string content) { TextBlock textBlock = new TextBlock { Text = content, Foreground = new SolidColorBrush(Colors.White), TextWrapping = TextWrapping.Wrap }; return textBlock; } // Create a default news item private UIElement CreateDefaultNewsItem(string message) { TextBlock textBlock = new TextBlock { Text = message, Foreground = new SolidColorBrush(Color.FromRgb(180, 180, 180)), FontStyle = FontStyles.Italic, TextWrapping = TextWrapping.Wrap }; return textBlock; } // Create an error news item private UIElement CreateNewsErrorItem(string errorMessage) { TextBlock textBlock = new TextBlock { Text = errorMessage, Foreground = new SolidColorBrush(Color.FromRgb(255, 100, 100)), FontStyle = FontStyles.Italic, TextWrapping = TextWrapping.Wrap }; return textBlock; } // Get default news content if server is unavailable private string GetDefaultNews() { // Create a sample JSON news array var defaultNews = new List { new NewsItem { Title = "Welcome to the Multi WoW Launcher!", Content = "This launcher is currently in alpha testing. Please report any bugs or issues you encounter.", Date = DateTime.Now.ToString("yyyy-MM-dd"), Type = "update" }, new NewsItem { Title = "Upcoming Maintenance", Content = "Server maintenance is scheduled for next Tuesday from 2 AM to 6 AM server time.", Date = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd"), Type = "maintenance" }, new NewsItem { Title = "Weekend Event: Double XP", Content = "Enjoy double XP gains this weekend! Perfect time to level up your alts.", Date = DateTime.Now.AddDays(-3).ToString("yyyy-MM-dd"), Type = "event" } }; return System.Text.Json.JsonSerializer.Serialize(defaultNews); } private void LoadEmbeddedVideo() { try { // Get the assembly that contains the embedded resource System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); // Build the resource name - it includes the namespace // Format: YourNamespace.FolderName.FileName.Extension string resourceName = "MultiWoWLauncher.Assets.bg.mp4"; // Get a stream of the embedded resource using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) { if (resourceStream != null) { // Create a temporary file to hold the video string tempFile = Path.Combine(Path.GetTempPath(), $"wow_bg_{Guid.NewGuid()}.mp4"); // Copy the embedded resource to the temporary file using (FileStream fileStream = new FileStream(tempFile, FileMode.Create)) { resourceStream.CopyTo(fileStream); } // Set the MediaElement source to the temporary file BackgroundVideo.Source = new Uri(tempFile); // Register for application exit to clean up the temp file Application.Current.Exit += (s, e) => { try { if (File.Exists(tempFile)) { File.Delete(tempFile); } } catch { // Ignore errors on cleanup } }; // Start playing the video BackgroundVideo.Play(); } else { // Resource not found, show fallback background ShowFallbackBackground(); Console.WriteLine("Video resource not found: " + resourceName); // To debug, list all available resources: string[] resources = assembly.GetManifestResourceNames(); Console.WriteLine("Available resources:"); foreach (string res in resources) { Console.WriteLine(" " + res); } } } } catch (Exception ex) { ShowFallbackBackground(); Console.WriteLine($"Error loading embedded video: {ex.Message}"); } } private void ShowFallbackBackground() { // Hide video element and show fallback BackgroundVideo.Visibility = Visibility.Collapsed; // If you have a fallback background element: if (FallbackBackground != null) { FallbackBackground.Visibility = Visibility.Visible; } // Otherwise, set a fallback on the parent Grid: else { var parent = BackgroundVideo.Parent as Panel; if (parent != null) { parent.Background = new LinearGradientBrush { StartPoint = new Point(0, 0), EndPoint = new Point(1, 1), GradientStops = new GradientStopCollection { new GradientStop(Color.FromRgb(10, 16, 33), 0), new GradientStop(Color.FromRgb(21, 40, 66), 1) } }; } } } private void LoadEmbeddedVideoInMemory() { try { // Get the assembly that contains the embedded resource System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); // Build the resource name with your namespace string resourceName = "MultiWoWLauncher.Assets.bg.mp4"; // Get a stream of the embedded resource using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) { if (resourceStream != null) { // Create a MemoryStream to hold the video data MemoryStream memoryStream = new MemoryStream(); // Copy the resource data to the memory stream resourceStream.CopyTo(memoryStream); memoryStream.Position = 0; // Create a BitmapSource from the memory stream var uri = new Uri("pack://application:,,,/" + resourceName); var streamResourceInfo = Application.GetResourceStream(uri); if (streamResourceInfo != null) { BackgroundVideo.Source = uri; BackgroundVideo.Play(); } else { // If direct URI approach fails, try MediaStreamSource (more complex) ShowFallbackBackground(); Console.WriteLine("Could not get resource stream for: " + resourceName); } } else { ShowFallbackBackground(); Console.WriteLine("Video resource not found: " + resourceName); // Debug info - list all resources string[] resources = assembly.GetManifestResourceNames(); Console.WriteLine("Available resources:"); foreach (string res in resources) { Console.WriteLine(" " + res); } } } } catch (Exception ex) { ShowFallbackBackground(); Console.WriteLine($"Error loading embedded video: {ex.Message}"); } } private void BackgroundVideo_MediaOpened(object sender, RoutedEventArgs e) { // When media is successfully opened, play it BackgroundVideo.Play(); } private void BackgroundVideo_MediaEnded(object sender, RoutedEventArgs e) { // Loop the video BackgroundVideo.Position = TimeSpan.Zero; BackgroundVideo.Play(); } private void BackgroundVideo_MediaFailed(object sender, ExceptionRoutedEventArgs e) { // If video fails to load, show the fallback background ShowFallbackBackground(); Console.WriteLine($"Media failed to load: {e.ErrorException.Message}"); } private void LaunchWoW_Click(object sender, RoutedEventArgs e) { try { // Try to get WoW path from config string wowPath = GetSettingFromConfig(WowPathKey); // Check if path is defined and valid if (string.IsNullOrEmpty(wowPath) || !File.Exists(wowPath)) { // Prompt user to select WoW directory if (!PromptForWowPath()) { // User cancelled the selection return; } // Get the path that was just saved wowPath = GetSettingFromConfig(WowPathKey); } // Check if user wants to clear cache bool clearCache = ClearCacheCheckBox.IsChecked ?? false; if (clearCache) { ClearWowCache(wowPath); } // Launch WoW and store the process Process wowProcess = Process.Start(wowPath); if (wowProcess != null) { // Give WoW a moment to start System.Threading.Thread.Sleep(1500); // Close the launcher this.Close(); } } catch (Exception ex) { MessageBox.Show($"Error launching World of Warcraft: {ex.Message}", "Launch Error", MessageBoxButton.OK, MessageBoxImage.Error); } } private bool PromptForWowPath() { MessageBox.Show("World of Warcraft executable path is not set. Please select your WoW.exe file.", "Setup Required", MessageBoxButton.OK, MessageBoxImage.Information); var dialog = new Microsoft.Win32.OpenFileDialog { DefaultExt = ".exe", Filter = "World of Warcraft|Wow.exe|All Executable Files (*.exe)|*.exe", Title = "Select World of Warcraft Executable", CheckFileExists = true }; // Try to set initial directory to common WoW installation paths string[] commonPaths = new[] { @"C:\Program Files (x86)\World of Warcraft\_retail_", @"C:\Program Files\World of Warcraft\_retail_", @"D:\World of Warcraft\_retail_", @"C:\Games\World of Warcraft\_retail_" }; foreach (string path in commonPaths) { if (Directory.Exists(path)) { dialog.InitialDirectory = path; break; } } if (dialog.ShowDialog() == true) { // User selected a file, save to config SaveWowPathToConfig(dialog.FileName); return true; } return false; } private void ClearCacheCheckBox_CheckedChanged(object sender, RoutedEventArgs e) { // Save the current state of the checkbox to config try { bool isChecked = ClearCacheCheckBox.IsChecked ?? false; SaveSettingToConfig(ClearCacheKey, isChecked.ToString()); } catch (Exception ex) { MessageBox.Show($"Error saving Clear Cache setting: {ex.Message}"); } } private void ClearWowCache(string wowPath) { try { // Get the WoW directory from the executable path string wowDirectory = Path.GetDirectoryName(wowPath); // Navigate up one level from _retail_ folder if needed if (Path.GetFileName(wowDirectory).Equals("_retail_", StringComparison.OrdinalIgnoreCase)) { wowDirectory = Directory.GetParent(wowDirectory).FullName; } // Common cache folder paths for WoW string[] cachePaths = new string[] { Path.Combine(wowDirectory, "_retail_", "Cache"), Path.Combine(wowDirectory, "Cache"), Path.Combine(wowDirectory, "_retail_", "WTF", "Cache") }; foreach (string cachePath in cachePaths) { if (Directory.Exists(cachePath)) { try { // Delete all files in the cache directory foreach (string file in Directory.GetFiles(cachePath, "*", SearchOption.AllDirectories)) { File.Delete(file); } // Delete all subdirectories foreach (string dir in Directory.GetDirectories(cachePath)) { Directory.Delete(dir, true); } } catch (Exception ex) { // continue } } } } catch (Exception ex) { // continue } } // Save WoW path to config private void SaveWowPathToConfig(string wowExecutablePath) { try { SaveSettingToConfig(WowPathKey, wowExecutablePath); MessageBox.Show($"World of Warcraft path set successfully to:\n{wowExecutablePath}", "Path Saved", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error saving WoW path to configuration: {ex.Message}", "Configuration Error", MessageBoxButton.OK, MessageBoxImage.Error); } } private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left) { this.DragMove(); } } private void CloseButton_Click(object sender, RoutedEventArgs e) { this.Close(); } // Method for manual refresh public async void ManuallyRefreshContent() { await RefreshAllContent(); } // Optional: Add this method for a refresh button private async void RefreshButton_Click(object sender, RoutedEventArgs e) { var button = sender as Button; if (button != null) { button.IsEnabled = false; } try { await RefreshAllContent(); } finally { if (button != null) { button.IsEnabled = true; } } } // Helper method to find a TextBlock by its Text property private TextBlock FindTextBlockByText(string text) { return FindVisualChild(this, tb => tb.Text == text); } // Generic method to find visual children private T FindVisualChild(DependencyObject parent, Func condition) where T : DependencyObject { int childCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childCount; i++) { DependencyObject child = VisualTreeHelper.GetChild(parent, i); if (child is T typedChild && condition(typedChild)) { return typedChild; } T result = FindVisualChild(child, condition); if (result != null) { return result; } } return null; } } }