WPF 简单实现电脑壁纸更换小工具与解决显示图片时内存占用过大的问题

参考

环境

软件/系统 版本 说明
Windows windows 10 专业版 22H2 64 位操作系统, 基于 x64 的处理器
Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.13.6
.NET Framework 4.8

问题

  1. 在 WPF 中,IsMouseOver 触发器偶尔不触发, Border.Background 为透明或未正确设置,可能导致鼠标命中测试区域与视觉区域不一致。
  2. DataContent 会自动继承,如果重写 ListBox 的模板,在 ItemTemplate 通过 Command="{Binding DataContext.SetWallpaperCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" 类似写法来获取命令, 通过 CommandParameter="{Binding}" 实现绑定当前项的对象,传递给命令。
  3. 如果展示高分率图片,会导致内存激增,通过设置 DecodePixelWidth DecodePixelHeight 来设置解析的大小,这样内存占用就很少了
    <Image>
    	<Image.Source>
    		<BitmapImage
    					 CacheOption="OnLoad"
    					 DecodePixelWidth="180"
    					 UriSource="{Binding FilePath}" />
    	</Image.Source>
    	<Image.Style>
    		<Style TargetType="{x:Type Image}">
    			<Setter Property="Stretch" Value="Fill" />
    			<Setter Property="Source" Value="{Binding FilePath}" />
    		</Style>
    	</Image.Style>
    </Image>
    

预览

image

部分代码

  1. MainWindow.xaml
    <Window
    	x:Class="WPFWallpaper.MainWindow"
    	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    	xmlns:local="clr-namespace:WPFWallpaper"
    	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    	xmlns:vm="clr-namespace:WPFWallpaper.ViewModel"
    	Title="WPFWallpaper"
    	Width="800"
    	Height="450"
    	WindowStartupLocation="CenterScreen"
    	mc:Ignorable="d">
    	<Window.DataContext>
    		<vm:MainWindowVM />
    	</Window.DataContext>
    	<DockPanel>
    		<Menu Style="{StaticResource MenuStyle}">
    			<MenuItem Command="{Binding SelectFolderCommand}" Header="选择文件夹" />
    			<MenuItem Command="{Binding AboutCommand}" Header="关于" />
    		</Menu>
    		<StatusBar DockPanel.Dock="Bottom">
    			<TextBlock Text="{Binding DirPath}" />
    		</StatusBar>
    		<ScrollViewer VerticalScrollBarVisibility="Auto">
    			<ListBox ItemsSource="{Binding ImageModels}" Style="{StaticResource ListStyle}" />
    		</ScrollViewer>
    	</DockPanel>
    </Window>
    
    
  2. MainWindowStyle.xaml
    <ResourceDictionary
    	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    	xmlns:model="clr-namespace:WPFWallpaper.Model">
    	<!--  菜单栏  -->
    	<Style x:Key="MenuStyle" TargetType="{x:Type Menu}">
    		<Setter Property="Height" Value="20" />
    		<Setter Property="DockPanel.Dock" Value="Top" />
    	</Style>
    	<!--  列表  -->
    	<Style x:Key="ListStyle" TargetType="{x:Type ListBox}">
    		<Setter Property="ScrollViewer.HorizontalAlignment" Value="Stretch" />
    		<Setter Property="BorderBrush" Value="Transparent" />
    		<Setter Property="BorderThickness" Value="0" />
    		<Setter Property="HorizontalAlignment" Value="Left" />
    		<Setter Property="VerticalAlignment" Value="Top" />
    		<Setter Property="ItemContainerStyle">
    			<Setter.Value>
    				<Style TargetType="{x:Type ListBoxItem}">
    					<Setter Property="Width" Value="190" />
    					<!--  如果不需要默认的列表外观样式,可以重写模板  -->
    					<Setter Property="Template">
    						<Setter.Value>
    							<ControlTemplate TargetType="{x:Type ListBoxItem}">
    								<ContentPresenter />
    							</ControlTemplate>
    						</Setter.Value>
    					</Setter>
    				</Style>
    			</Setter.Value>
    		</Setter>
    		<Setter Property="ItemTemplate">
    			<Setter.Value>
    				<DataTemplate DataType="{x:Type model:ImageModel}">
    					<StackPanel
    						Margin="10,10,0,0"
    						HorizontalAlignment="Center"
    						VerticalAlignment="Center">
    						<Image>
    							<Image.Source>
    								<BitmapImage
    									CacheOption="OnLoad"
    									DecodePixelWidth="180"
    									UriSource="{Binding FilePath}" />
    							</Image.Source>
    							<Image.Style>
    								<Style TargetType="{x:Type Image}">
    									<Setter Property="Stretch" Value="Fill" />
    									<Setter Property="Source" Value="{Binding FilePath}" />
    								</Style>
    							</Image.Style>
    						</Image>
    						<Button Command="{Binding DataContext.SetWallpaperCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding}">
    							<Button.Style>
    								<Style TargetType="{x:Type Button}">
    									<Setter Property="Margin" Value="0,6,0,0" />
    									<Setter Property="Height" Value="30" />
    									<Setter Property="BorderThickness" Value="0" />
    									<Setter Property="BorderBrush" Value="Transparent" />
    									<Setter Property="Template">
    										<Setter.Value>
    											<ControlTemplate TargetType="{x:Type Button}">
    												<Border
    													x:Name="Border"
    													Background="#FFF"
    													BorderBrush="#CCC"
    													BorderThickness="1"
    													CornerRadius="6">
    													<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    												</Border>
    												<ControlTemplate.Triggers>
    													<Trigger Property="IsMouseOver" Value="True">
    														<Setter Property="Foreground" Value="#FFF" />
    														<Trigger.EnterActions>
    															<BeginStoryboard>
    																<Storyboard>
    																	<ColorAnimation
    																		Storyboard.TargetProperty="Foreground.Color"
    																		From="#000000"
    																		To="#FFFFFF"
    																		Duration="0:0:0.3" />
    																	<ColorAnimation
    																		Storyboard.TargetName="Border"
    																		Storyboard.TargetProperty="Background.Color"
    																		From="#FFF"
    																		To="#31bdec"
    																		Duration="0:0:0.3" />
    																</Storyboard>
    															</BeginStoryboard>
    														</Trigger.EnterActions>
    													</Trigger>
    													<Trigger Property="IsMouseOver" Value="False">
    														<Setter TargetName="Border" Property="Background" Value="#16b777" />
    														<Setter Property="Foreground" Value="#000" />
    														<Trigger.EnterActions>
    															<BeginStoryboard>
    																<Storyboard>
    																	<ColorAnimation
    																		Storyboard.TargetProperty="Foreground.Color"
    																		From="#FFFFFF"
    																		To="#000000"
    																		Duration="0:0:0.3" />
    																	<ColorAnimation
    																		Storyboard.TargetName="Border"
    																		Storyboard.TargetProperty="Background.Color"
    																		From="#31bdec"
    																		To="#FFF"
    																		Duration="0:0:0.3" />
    																</Storyboard>
    															</BeginStoryboard>
    														</Trigger.EnterActions>
    													</Trigger>
    													<Trigger Property="IsPressed" Value="True">
    														<Setter Property="Foreground" Value="#FFF" />
    														<Trigger.EnterActions>
    															<BeginStoryboard>
    																<Storyboard>
    																	<ColorAnimation
    																		Storyboard.TargetName="Border"
    																		Storyboard.TargetProperty="Background.Color"
    																		From="#31bdec"
    																		To="#1e9fff"
    																		Duration="0:0:0.1" />
    																</Storyboard>
    															</BeginStoryboard>
    														</Trigger.EnterActions>
    													</Trigger>
    												</ControlTemplate.Triggers>
    											</ControlTemplate>
    										</Setter.Value>
    									</Setter>
    								</Style>
    							</Button.Style>
    							<Button.Content>
    								设为壁纸
    							</Button.Content>
    						</Button>
    					</StackPanel>
    				</DataTemplate>
    			</Setter.Value>
    		</Setter>
    		<Setter Property="ItemsPanel">
    			<Setter.Value>
    				<ItemsPanelTemplate>
    					<WrapPanel Margin="0" Orientation="Horizontal" />
    				</ItemsPanelTemplate>
    			</Setter.Value>
    		</Setter>
    		<Setter Property="Template">
    			<Setter.Value>
    				<ControlTemplate>
    					<ItemsPresenter />
    				</ControlTemplate>
    			</Setter.Value>
    		</Setter>
    	</Style>
    
    </ResourceDictionary>
    
  3. MainWindowVM.cs
    using Microsoft.Win32;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Input;
    using WPFWallpaper.Model;
    using WPFWallpaper.Utilities;
    using System.Windows.Forms;
    using System.Threading;
    using static System.Net.Mime.MediaTypeNames;
    using System.IO;
    using WPFWallpaper.View;
    namespace WPFWallpaper.ViewModel
    {
    	public class MainWindowVM : Utilities.ViewModelBase
    	{
    		/// <summary>
    		/// 图片列表
    		/// </summary>
    		private ObservableCollection<ImageModel> _imageModels { get; set; } = new ObservableCollection<ImageModel>();
    		public ObservableCollection<ImageModel> ImageModels {
    			get { return _imageModels; }
    			set { _imageModels = value; OnPropertyChanged(); }
    		}
    		/// <summary>
    		/// 壁纸所在文件夹
    		/// </summary>
    		private string _dirPath { get; set; } = "";
    		public string DirPath
    		{
    			get { return _dirPath; }
    			set { _dirPath = value; OnPropertyChanged(); }
    		}
    
    		public MainWindowVM()
    		{
    			SelectFolderCommand = new RelayCommand(SelectFolderEvent);
    			SetWallpaperCommand = new RelayCommand(SetWallpaperEvent);
    			AboutCommand = new RelayCommand(AboutEvent);
    			//
    			if (Properties.Settings.Default.DirPath != null && Properties.Settings.Default.DirPath != "")
    			{
    				ReadFolder(Properties.Settings.Default.DirPath);
    			}
    		}
    		/// <summary>
    		/// 选择文件夹
    		/// </summary>
    		public ICommand SelectFolderCommand { get; set; }
    		private void SelectFolderEvent(object obj)
    		{
    			using (FolderBrowserDialog openFileDialog = new FolderBrowserDialog()) { 
    				if (openFileDialog.ShowDialog() == DialogResult.OK)
    				{
    					ReadFolder(openFileDialog.SelectedPath);
    				}
    			}
    		}
    		/// <summary>
    		/// 更新壁纸列表
    		/// </summary>
    		/// <param name="dirPath"></param>
    		private void ReadFolder(string dirPath)
    		{
    			ImageModels.Clear();
    			var files = FileHelp.GetAllFilterFiles(dirPath, FileHelp.ImageTypes);
    			foreach (var file in files)
    			{
    				ImageModels.Add(new ImageModel()
    				{
    					FileName = Path.GetFileNameWithoutExtension(file),
    					FilePath = file,
    				});
    			}
    			// 
    			if(Properties.Settings.Default.DirPath != dirPath)
    			{
    				Properties.Settings.Default.DirPath = dirPath;
    				Properties.Settings.Default.Save();
    			}
    			//
    			DirPath = dirPath;
    		}
    		/// <summary>
    		/// 关于
    		/// </summary>
    		public ICommand AboutCommand { get; set; }
    		private void AboutEvent(object obj)
    		{
    			AboutView aboutView  = new AboutView();
    			aboutView.ShowDialog();
    		}
    		/// <summary>
    		/// 设置壁纸
    		/// </summary>
    		public ICommand SetWallpaperCommand { get; set; }
    		private void SetWallpaperEvent(object obj)
    		{
    			if (obj is ImageModel imageModel)
    			{
    				if (!WallpaperHelp.ChangeWallPaper(imageModel.FilePath))
    				{
    					MessageBox.Show("更换失败");
    				}
    			}
    		}
    	}
    }
    
    
  4. FileHelp.cs
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms.VisualStyles;
    
    namespace WPFWallpaper.Utilities
    {
    	public class FileHelp
    	{
    		/// <summary>
    		/// 图片类型
    		/// </summary>
    		public static readonly string[] ImageTypes = new string[] { "*.png", "*.jpg", "*.jpeg" };
    		/// <summary>
    		/// 获取所有过滤的文件
    		/// </summary>
    		/// <param name="path"></param>
    		public static List<string> GetAllFilterFiles(string path, string[] types)
    		{
    			List<string> files  = new List<string>();
    			// 扫描音乐
    			foreach (var item in types)
    			{
    				files.AddRange(Directory.EnumerateFiles(path, item, SearchOption.AllDirectories));
    			}
    			return files;
    		}
    	}
    }
    
    
    
  5. WallpaperHelp.cs
    using Microsoft.Win32;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace WPFWallpaper.Utilities
    {
    	public class WallpaperHelp
    	{
    		[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
    		public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
    
    		/// <summary>
    		/// 更换壁纸
    		/// </summary>
    		/// <param name="fileName">壁纸文件的路径</param>
    		/// <returns>操作结果:true为更换成功,false为更换失败</returns>
    		public static bool ChangeWallPaper(string fileName)
    		{
    			try
    			{
    				if (string.IsNullOrEmpty(fileName))
    				{
    					return false;
    				}
    				if (File.Exists(fileName) == false)
    				{
    					return false;
    				}
    				var nResult = SystemParametersInfo(20, 1, Path.GetFullPath(fileName), 0x1 | 0x2); //更换壁纸
    				if (nResult == 0)
    				{
    					return false;
    				}
    				return true;
    			}catch(Exception e)
    			{
    				Debug.WriteLine("修改壁纸出现错误" + e.Message);
    				return false;
    			}
    
    		}
    	}
    }
    
posted @ 2025-05-26 17:16  夏秋初  阅读(39)  评论(0)    收藏  举报