Медиаплеер - Как динамически изменять стиль элемента через код?
Во время компиляции программа создает несколько кнопок и присваивает им стиль, мне нужно изменить параметры name и autor во время компиляции, как это реализовать?

<Window x:Class="SoundPlayer.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SoundPlayer"
mc:Ignorable="d"
Title="MainWindow" MinHeight="400" MinWidth="700"
Width="700" Height="400"
Background="#FF212121">
<Window.Resources>
<Style TargetType="Button" x:Key="songStyle">
<Style.Setters>
<Setter Property="Height" Value="40"/>
<Setter Property="Margin" Value="2,1,2,1"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="borderSongButton" BorderThickness="0" Background="#FF324D95">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="5"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="numberTextBlock" Grid.Column="1" Text="1."
FontSize="30" VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="#FF9B9B9B"/>
<Grid x:Name="songInfoGrid" Grid.Column="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="80"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="auto" MinWidth="80" MaxWidth="150"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="nameTitleTextBlock" Text="Название:"
VerticalAlignment="Center"
Foreground="#FF9B9B9B"/>
<TextBlock x:Name="songNameTextBlock" Text="NAME"
Grid.Row="1" FontSize="25"
VerticalAlignment="Center" Foreground="#FF272727"/>
<Border Grid.Column="1" Grid.RowSpan="2" Background="#FF9B9B9B" Width="1"/>
<TextBlock x:Name="autorTitleTextBlock" Text="Исполнитель:"
Foreground="#FF9B9B9B" Grid.Column="2"
VerticalAlignment="Center"/>
<TextBlock x:Name="autorTextBlock" Text="autor"
Grid.Column="2" Grid.Row="1"
VerticalAlignment="Center"
FontSize="25" Foreground="#FF272727"/>
<Border Grid.Column="3" Grid.RowSpan="2" Background="#FF9B9B9B" Width="1"/>
<TextBlock x:Name="durationTextBlock" Text="00:00"
Grid.Column="5" Grid.RowSpan="2"
VerticalAlignment="Center"
Foreground="#FF272727" FontSize="20"/>
</Grid>
<Button x:Name="optionButton" Grid.Column="5" Height="30"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</Window.Resources>
<Grid x:Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="323*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="60*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300*"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="800*"/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<Rectangle Fill="#FF252525" RadiusX="10" RadiusY="10"/>
<Border x:Name="playlistBorder" Grid.Column="2" Grid.Row="0" Background="#FF242742" CornerRadius="10"/>
<Border Grid.Column="0" Grid.Row="2" Background="#FF242742" CornerRadius="10"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Grid x:Name="sortGrid" Grid.RowSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="27*"/>
<ColumnDefinition Width="1.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Border Grid.ColumnSpan="6" Grid.RowSpan="3" Background="#FF3A3A3A" CornerRadius="10,10,0,0"/>
<ComboBox x:Name="sortComboBox" Grid.Row="1" Grid.Column="1"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center">
<TextBlock Text="по названию"/>
<TextBlock Text="по длине"/>
<TextBlock Text="по исполнителю"/>
</ComboBox>
<Button x:Name="sortButton" Grid.Row="1" Grid.Column="3"/>
</Grid>
<ScrollViewer x:Name="playListScrollView" Grid.Row="2" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="playListStackPanel" Grid.Row="1">
<Button Height="40"/>
<Button Height="40"/>
<Button Height="40"/>
</StackPanel>
</ScrollViewer>
</Grid>
<Grid Grid.Row="2" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="9*"/>
<ColumnDefinition Width="127*"/>
<ColumnDefinition Width="38*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="7*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Button Grid.Row="1" Grid.Column="2"/>
</Grid>
<Grid x:Name="navigationGrid" Grid.Column="2" Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="7*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="70*"/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<ScrollBar Orientation="Horizontal" Grid.Row="0" Grid.Column="1" Height="20"/>
<TextBlock x:Name="currentTimeTextBlock" Text="00:00" VerticalAlignment="Center" TextAlignment="Center" Foreground="#FFA8A8A8" FontSize="20"/>
<TextBlock x:Name="maxTimeTextBlock" Text="00:00" VerticalAlignment="Center" TextAlignment="Center" Foreground="#FFA8A8A8" FontSize="20" Grid.Column="2"/>
<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="30*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="playerButtonsGrid" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="6*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="6*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0"/>
<Button x:Name="pauseButton" Grid.Column="2"/>
<Button Grid.Column="4"/>
</Grid>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="80*"/>
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<Border x:Name="volumeBorder" Grid.Column="1" Grid.ColumnSpan="2" Background="#FF242742" CornerRadius="10"/>
<Grid x:Name="volumeGrid" Grid.Column="1" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="25*"/>
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<ScrollBar x:Name="VolumeScroll" Orientation="Horizontal" Grid.Row="1" Grid.Column="1"/>
<ToggleButton x:Name="switchVolumeButton" Grid.Row="1" Grid.Column="3"/>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</Window>
public MainWindow()
{
InitializeComponent();
AddSongButtonToPlayList();
}
public void AddSongButtonToPlayList()
{
Button songButton = new Button();
songButton.Style = this.Resources["songStyle"] as Style;
//изменение name и autor
playListStackPanel.Children.Add(songButton);
}
Ответы (1 шт):
Чтож, вы мне показали дизайн без начинки, я вам покажу начинку без дизайна.
Простой аудиоплеер в WPF
Решение может:
- Переходить по каталогам на текущем диске
- Добавлять MP3 файл в плейлист - двойной клик по файлу в левой панели
- Воспроизводить файл из плейлиста - двойной клик по файлу в плейлисте или кнопка Play
- Воспроизводить следующий файл, если текущий закончился автоматически
- Ставить воспроизведение на паузу - кнопка Pause
- Показывать, сколько времени прошло, продолжительность трека и прогресс воспроизведения в
ProgressBar - Менять позицию воспроизведения при клике на прогрессбар
- Регулировать громкость
Может приложение не много, но и не мало. Я старался сделать так, чтобы пример выглядел как можно проще, но при этом показать самые основные принципы, которые вы сможете переиспользовать при дальнейшей разработке.
Я выбрал стандартный MediaPlayer в качестве аудиодвижка для простоты примера, но он мало чего умеет. Советую присмотреться к библиотеке NAudio.
В основе построения приложения лежит шаблон проектирования MVVM. Я не буду вдаваться в детали и рассказывать, что это такое, но вы можете почитать в других моих ответах, например здесь или здесь. Там же вы найдете информацию про вспомогательные классы, показанные ниже.
Прямо отвечая на ваш вопрос - как менять данные в интерфейсе из кода, отвечу, что надо сделать следующие шаги:
- Создать класс, в котором разместить свойство, являющееся источником данных для контрола. Например в решении ниже такой класс -
MainViewModel - Класс, содержащий нужное свойство должен реализовывать интерфейс
INotifyPropertyChanged. У меня реализация интерфейса наследуется из классаNotifyPropertyChanged, представленного ниже. - Свойство, которое предоставляет данные должно оповещать интерфейс об изменениях данныз посредством вызова события
PropertyChangedпередавая в качестве аргумента своё имя. Данный механизм у меня реализован в методеOnPropertyChanged()где имя вызвавшего метод свойства автоматически подсталяется компилятором с помощью аттрибута[CallerMemberName]. - Контрол нужно привязать к свойству с помощью
Bindingнапример<TextBlock Text="{Binding MyText}"/> - Чтобы
Bindingпонял, где искать нужное свойство с данными, окну надо задатьDataContext. Это сделано у меня в конструкторе окнаMainWindow.
Далее можно менять значение свойства, а контрол при этом будет обновлять своё содержимое автоматически.
С первого взгляда может показаться сложным, но оно того стоит, потому что полностью избавляет от войны с контролами в C# коде. Я вообще не работаю с контролами в коде, все контролы находятся в разметке. Есть конечно в коде MediaPlayer, он контрол, но я его не использую в разметке интерфейса.
Теперь к делу
Вспомогательные классы
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
=> (_execute, _canExecute) = (execute, canExecute);
public bool CanExecute(object parameter)
=> _canExecute == null || _canExecute(parameter);
public void Execute(object parameter)
=> _execute(parameter);
}
Структуры данных
public enum FileItemType
{
File,
Directory
}
public class FileItem
{
public FileItemType Type { get; }
public string FilePath { get; }
public string Name => Path.GetRelativePath(Environment.CurrentDirectory, FilePath);
public FileItem(FileItemType type, string path)
{
Type = type;
FilePath = path;
}
public override bool Equals(object obj)
=> obj is FileItem item && FilePath.Equals(item.FilePath);
public override int GetHashCode()
=> FilePath.GetHashCode();
}
View Model класс
Здесь весь основной код
public class MainViewModel : NotifyPropertyChanged
{
const string fileMask = "*.mp3";
private readonly MediaPlayer _player = new MediaPlayer();
private List<FileItem> _files;
private string _currentDirectory;
private ICommand _changeDirCommand;
private ICommand _PlayCommand;
private ObservableCollection<FileItem> _playList;
private bool _isPlaying;
private bool _isPaused = true;
private CancellationTokenSource _cts;
private Task _playingTask;
private FileItem _currentTrack;
private TimeSpan _duration;
private TimeSpan _position;
private ICommand _pauseResumeCommand;
public double Volume
{
get => _player.Volume;
set => _player.Volume = value;
}
public TimeSpan Duration
{
get => _duration;
set
{
if (value != _duration)
{
_duration = value;
OnPropertyChanged();
}
}
}
public TimeSpan Position
{
get => _position;
set
{
if (value != _position)
{
_position = value;
OnPropertyChanged();
}
}
}
public FileItem CurrentTrack
{
get => _currentTrack;
set
{
_currentTrack = value;
OnPropertyChanged();
}
}
public bool IsPlaying
{
get => _isPlaying;
set
{
_isPlaying = value;
OnPropertyChanged();
}
}
public bool IsPaused
{
get => _isPaused;
set
{
_isPaused = value;
if (_isPaused)
{
if (_player.CanPause)
_player.Pause();
}
else
_player.Play();
OnPropertyChanged();
}
}
public List<FileItem> Files
{
get => _files;
set
{
_files = value;
OnPropertyChanged();
}
}
public double Progress
{
get
{
if (_player.NaturalDuration.HasTimeSpan)
{
Duration = _player.NaturalDuration.TimeSpan;
Position = _player.Position;
return Position.TotalSeconds * 100 / Duration.TotalSeconds;
}
return 0;
}
set
{
if (_player.NaturalDuration.HasTimeSpan)
{
Position = TimeSpan.FromSeconds(value * Duration.TotalSeconds / 100);
_player.Position = Position;
OnPropertyChanged();
}
}
}
public ObservableCollection<FileItem> PlayList
{
get => _playList;
set
{
_playList = value;
OnPropertyChanged();
}
}
public string CurrentDirectory
{
get => _currentDirectory;
set
{
_currentDirectory = value;
OnPropertyChanged();
}
}
private void LoadDirectories()
{
CurrentDirectory = Environment.CurrentDirectory;
var files = new List<FileItem>();
if (Path.GetPathRoot(CurrentDirectory) != CurrentDirectory)
files.Add(new FileItem(FileItemType.Directory, Path.Combine(CurrentDirectory, "..")));
files.AddRange(Directory.GetDirectories(CurrentDirectory).Select(d => new FileItem(FileItemType.Directory, d)));
files.AddRange(Directory.GetFiles(CurrentDirectory, fileMask).Select(f => new FileItem(FileItemType.File, f)));
Files = files;
}
public ICommand ChangeDirCommand => _changeDirCommand ??= new RelayCommand(parameter =>
{
if (parameter is FileItem item)
{
switch (item.Type)
{
case FileItemType.Directory:
Directory.SetCurrentDirectory(item.Name);
LoadDirectories();
break;
case FileItemType.File:
PlayList.Add(item);
break;
}
}
});
public ICommand PlayCommand => _PlayCommand ??= new RelayCommand(parameter =>
{
Play();
});
public ICommand PauseResumeCommand => _pauseResumeCommand ??= new RelayCommand(_ =>
{
if (!IsPlaying)
Play();
else
IsPaused = !IsPaused;
}, _ => CurrentTrack != null);
private async void Play()
{
if (CurrentTrack == null)
return;
if (_cts != null)
{
_cts?.Cancel();
await _playingTask;
}
_playingTask = PlayAsync(CurrentTrack);
}
private void PlayNext()
{
if (PlayList.Count > 1)
{
int index = PlayList.IndexOf(CurrentTrack);
if (index < PlayList.Count - 1)
{
CurrentTrack = PlayList[index + 1];
Play();
}
else
IsPaused = true;
}
else
IsPaused = true;
}
private async Task PlayAsync(FileItem item)
{
IsPlaying = true;
_player.Open(new Uri(item.FilePath));
IsPaused = false;
using (_cts = new CancellationTokenSource())
{
try
{
while (true)
{
OnPropertyChanged(nameof(Progress));
await Task.Delay(500, _cts.Token);
}
}
catch (OperationCanceledException)
{ }
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
_cts = null;
IsPlaying = false;
Progress = 0;
}
public void Start()
{
_player.MediaEnded += (s, e) => { _cts?.Cancel(); PlayNext(); };
PlayList = new ObservableCollection<FileItem>();
LoadDirectories();
}
}
Подключение View Model к окну
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
DataContext = vm;
Loaded += (s, e) => vm.Start();
}
}
Кликабельный прогрессбар
Нужен, чтобы можно было в него тыкать и менять текущее время воспроизведения
public class EditableProgressBar : ProgressBar
{
public EditableProgressBar() : base()
=> Cursor = Cursors.Hand;
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Value = e.GetPosition(this).X * (Maximum - Minimum) / ActualWidth;
}
}
Разметка интерфейса
<Window x:Class="WpfApp2.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" d:DataContext="{d:DesignInstance local:MainViewModel}">
<Window.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Files}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Name}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Directory">
<Setter Property="Text" Value="{Binding Name, StringFormat=[{0}]}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.ChangeDirCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Column="1" ItemsSource="{Binding PlayList}" SelectedItem="{Binding CurrentTrack}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.PlayCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Margin="10" Grid.Row="1" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Position, StringFormat=mm\\:ss}" Padding="5" HorizontalAlignment="Center"/>
<local:EditableProgressBar Grid.Column="1" Value="{Binding Progress}"/>
<TextBlock Grid.Column="2" Text="{Binding Duration, StringFormat=mm\\:ss}" Padding="5" HorizontalAlignment="Center"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Slider VerticalAlignment="Center" Margin="10" Value="{Binding Volume}" Maximum="1" LargeChange="0.1" SmallChange="0.01"/>
<Button Grid.Column="1" FontFamily="Segoe MDL2 Assets" Margin="10" FontSize="20" Command="{Binding PauseResumeCommand}" Padding="10 0">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content" Value=""/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsPlaying}" Value="True"/>
<Condition Binding="{Binding IsPaused}" Value="False"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Content" Value=""/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Grid>
</Window>
Данный ответ написан под музыку, которую играл этот плеер.
Решение в сборе на Яндекс.Диске - https://disk.yandex.ru/d/YYS4u6M7viiLug