Button doesn't bind to function in viewmodel with caliburn micro - c#

I have the following xaml code in my view:
<UserControl x:Class="Klanten_beheer.Views.CustomerListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit">
<DockPanel LastChildFill="True" Height="Auto" Width="Auto" Grid.Row="0">
<Button x:Name="ToLastRecord" Content="<<" Background="LightSkyBlue"/>
And the following code in my viewmodel:
public class CustomerListViewModel : PropertyChangedBase
{
#region Fields
private IObservableCollection<Client> m_clients;
#endregion
#region Properties
public int Position { get; private set; }
public IObservableCollection<Client> Clients
{
get { return Clients; }
set { Clients = value; }
}
public Client SelectedClient
{
get
{
if ((Position < m_clients.Count) && (Position > -1))
{
return m_clients[Position];
}
else
{
return m_clients[0];
}
}
set
{
Position = m_clients.IndexOf(m_clients.Where(x => x.ClientNumber == value.ClientNumber).FirstOrDefault());
}
}
#endregion
#region Constructors
CustomerListViewModel(int position)
{
using (ClientDbEntities ctx = new ClientDbEntities())
{
var m_clients = (from client in ctx.Clients
orderby client.ClientNumber
select client).ToList();
if (m_clients.Count != 0 && position < m_clients.Count)
{
Position = position;
}
else if (position >= m_clients.Count)
{
// Do nothing
}
else
{
Position = -1;
}
}
}
public CustomerListViewModel()
{
Position = 0;
}
#endregion
#region Public functions
public bool CanToLastRecord()
{
return true;
}
public void ToLastRecord()
{
System.Windows.MessageBox.Show("ToLastRecord");
}
#endregion
}
}
I would expect that the function ToLastRecord was trigger when the button is pushed.
For some reason the function is never triggert.

I know it's been awhile - but forever who'll come up to this,
you should really take a look at Caliburn.micro cheat-sheet
The answer is right there at the top of the page - in order to attach an action to a default event (on buttons clicks do count) you need to use
<Button x:Name="ToLastRecord" Content="<<" Background="LightSkyBlue" cal:Message.Attach="ToLastRecord"/>
Don't forget to add the following line to UserControl element:
xmlns:cal="http://www.caliburnproject.org"

Related

How to randomize images in a slotmachine C#

So I'm making a slot machine in C#. I'm really new to C# and I am really bad at it.
Up to this point my project has been going fine. But now I want to randomize the images shown, when the 'spin' Button is clicked.
I've tried a lot of different things. The solutions I have found are either with the use of a PictureBox or nothing close to what I'm working on.
If someone could take a look at my code and push me in the right direction, I would be really grateful.
Thanks in advance!
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace Prb.Slot.Machine.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
int CoinInsert = 0;
private static Random random;
public enum SlotMachineIcon
{
Banana,
BigWin,
Cherry,
Lemon,
Orange,
Plum,
Seven,
Strawberry,
Watermelon
}
public MainWindow()
{
InitializeComponent();
}
private static void Init()
{
if (random == null) random = new Random();
}
public static int Random(int min, int max)
{
Init();
return random.Next(min, max);
}
void UpdateImage(Image wpfImage, SlotMachineIcon newIcon)
{
DirectoryInfo directoryInfo = new DirectoryInfo(Environment.CurrentDirectory);
directoryInfo = new DirectoryInfo(directoryInfo.Parent.Parent.Parent.Parent.FullName);
Uri uri = new Uri($"{directoryInfo.FullName}/images/{newIcon}.png");
wpfImage.Source = new BitmapImage(uri);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lblCoinsInserted.Content = 0;
lblCoinBalance.Content = 0;
lblCoinsWon.Content = 0;
UpdateImage(imgLeft, SlotMachineIcon.Cherry);
UpdateImage(imgMiddle, SlotMachineIcon.Banana);
UpdateImage(imgRight, SlotMachineIcon.Seven);
}
private void btnInsertCoins_Click(object sender, RoutedEventArgs e)
{
int.TryParse(txtInsertCoins.Text, out int InsertCoins);
if (InsertCoins > 0)
{
CoinInsert += int.Parse(txtInsertCoins.Text.ToString());
lblCoinBalance.Content = (int)lblCoinBalance.Content + Convert.ToInt32(txtInsertCoins.Text);
lblCoinsInserted.Content = CoinInsert;
txtInsertCoins.Clear();
}
else
{
MessageBox.Show("Gelieve strikt positieve getallen in te vullen", "Ongeldig aantal munten", MessageBoxButton.OK, MessageBoxImage.Warning);
txtInsertCoins.Clear();
}
}
private void btnSpin_Click(object sender, RoutedEventArgs e)
{
int InsertedCoins = Convert.ToInt32(lblCoinsInserted.Content);
int CoinsBalance = Convert.ToInt32(lblCoinBalance.Content);
/*var v = Enum.GetValues(typeof(SlotMachineIcon));
int number = random.Next(10);*/
if (InsertedCoins == 0 | CoinsBalance == 0)
{
MessageBox.Show("Gelieve eerst munten in te werpen", "Geen munten ingeworpen", MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
lblCoinBalance.Content = CoinsBalance - 1;
UpdateImage(imgLeft, SlotMachineIcon.Strawberry);
UpdateImage(imgMiddle, SlotMachineIcon.Watermelon);
UpdateImage(imgRight, SlotMachineIcon.Watermelon);
}
}
}
}
Edit: moved out random declaration as #EmondErno pointed it out.
This method returns a random icon every time you call it:
private Random random = new();
private SlotMachineIcon GetRandomIcon()
{
return (SlotMachineIcon)random.Next(10); //don't forget to update this number if you add or remove icons
}
Then call it in every UpdateImage method like:
UpdateImage(imgLeft, GetRandomIcon());
UpdateImage(imgMiddle, GetRandomIcon());
UpdateImage(imgRight, GetRandomIcon());
You're trying to do everything in the code behind, which is a terrible mistake for many reasons, among which your program will get hard to maintain read and update at some point and you are tight coupling the view and the logic of your program. You want to follow the MVVM pattern and put only in the code behind only the logic of the view (no data).
Also in your code, you're reinventing the updating system that already exists in WPF, you want to use the databinding and WPF updating system and get rid of all "update icon" logic in your program.
This is a ViewModel that you could use (.net 5.0):
public class SlotViewModel: ISlotViewModel, INotifyPropertyChanged
{
private Random _r = new();
private int _slotChoicesCount;
private SlotSet _currentSlotSet;
private ICommand _spinCommand;
public SlotViewModel()
{
_slotChoicesCount = Enum.GetNames(typeof(SlotMachineIcon)).Length;
}
private SlotSet GetNewSet() => new(Enumerable.Range(0,3).Select(o => (SlotMachineIcon)_r.Next(_slotChoicesCount)).ToList());
public SlotSet CurrentSlotSet
{
get => _currentSlotSet;
set
{
if (Equals(value, _currentSlotSet)) return;
_currentSlotSet = value;
OnPropertyChanged();
}
}
public ICommand SpinCommand => _spinCommand ??= new DelegateCommand(s => { CurrentSlotSet = GetNewSet(); }, s => true);
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The most important part is that your ViewModel implements INotifyPropertyChanged. When you uses SpinCommand, it updates the property CurrentSlotSet, and that's all you need to worry about. All the rest is taken care of by the WPF databinding system.
SlotSet is a convenient way to present an immutable result:
public class SlotSet
{
public SlotMachineIcon Left { get; }
public SlotMachineIcon Middle { get; }
public SlotMachineIcon Right { get; }
public SlotSet(IList<SlotMachineIcon> triad)
{
Left = triad[0];
Middle = triad[1];
Right = triad[2];
}
public bool IsWinner => Left == Middle && Middle == Right; // just an example
}
ISlotViewModel is the interface (contract) that your ViewModel satisfies.
public interface ISlotViewModel
{
ICommand SpinCommand { get; }
SlotSet CurrentSlotSet { get; set; }
}
The helper class DelegateCommand:
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
In your View, XAML part, your only need something as simple as this:
<Button Command="{Binding SpinCommand}">spin</Button>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding CurrentSlotSet.Left}"/>
<Image Source="{Binding CurrentSlotSet.Middle}"/>
<Image Source="{Binding CurrentSlotSet.Right}"/>
</StackPanel>
And in the Windows markup has this:
xmlns:local="clr-namespace:SlotMachine"
d:DataContext="{d:DesignInstance Type=local:SlotViewModel, IsDesignTimeCreatable=True}"
The code behind is as simple as this:
public ISlotViewModel ViewModel
{
get { return (ISlotViewModel)DataContext; }
set { DataContext = value; }
}
public SlotView() // or MainWindow
{
InitializeComponent();
ViewModel = new SlotViewModel();
}
The only thing missing here is to add a converter in each of your <Image, which will convert a SlotMachineIcon value into the image path.
PS: if you don't have resharper, you may need this class too:
[AttributeUsage(AttributeTargets.Method)]
public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
{
public NotifyPropertyChangedInvocatorAttribute() { }
public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
{
ParameterName = parameterName;
}
[CanBeNull] public string ParameterName { get; }
}

Slider do not work after changing track NAudio

I have slider as trackbar for soundtrack timeline.Soundtrack play from web using NAudion. All code using from NAudio WPF example.I changed only accessibility modifiers.First time when I start play first track all works good.But if I change to next track, slider still in start.And changing only if I click Pause then Play.
For fully understand:
First track - slider works and moves.
Change to the next track - slider at the beginning and doesn't move. But it begins to move after pressing Pause, then Play. It immediately moves to the place where the playback is at the moment and continues normal operation. And so with each following track.
Code of VM for PlayerUserControl:
public class AudioControlVM : ViewModelBase, IDisposable
{
private AudioModel _currentSong;
public AudioModel CurrentSong { get { return _currentSong; } set { _currentSong = value; RaisePropertyChanged("CurrentSong"); } }
private string inputPath, songName;
private string defaultDecompressionFormat;
public IWavePlayer wavePlayer { get; set; }
private WaveStream reader;
public RelayCommand PlayCommand { get; set; }
public RelayCommand PauseCommand { get; set; }
public RelayCommand StopCommand { get; set; }
public DispatcherTimer timer = new DispatcherTimer();
private double sliderPosition;
private readonly ObservableCollection<string> inputPathHistory;
private string lastPlayed;
public AudioControlVM()
{
inputPathHistory = new ObservableCollection<string>();
PlayCommand = new RelayCommand(() => Play());
PauseCommand = new RelayCommand(() => Pause());
StopCommand = new RelayCommand(Stop, () => !IsStopped);
timer.Interval = TimeSpan.FromMilliseconds(500);
timer.Tick += TimerOnTick;
}
public bool IsPlaying => wavePlayer != null && wavePlayer.PlaybackState == PlaybackState.Playing;
public bool IsStopped => wavePlayer == null || wavePlayer.PlaybackState == PlaybackState.Stopped;
public IEnumerable<string> InputPathHistory => inputPathHistory;
const double SliderMax = 10.0;
private void TimerOnTick(object sender, EventArgs eventArgs)
{
if (reader != null)
{
sliderPosition = reader.Position * SliderMax / reader.Length;
RaisePropertyChanged("SliderPosition");
}
}
public double SliderPosition
{
get => sliderPosition;
set
{
if (sliderPosition != value)
{
sliderPosition = value;
if (reader != null)
{
var pos = (long)(reader.Length * sliderPosition / SliderMax);
reader.Position = pos; // media foundation will worry about block align for us
}
RaisePropertyChanged("SliderPosition");
}
}
}
private bool TryOpenInputFile(string file)
{
bool isValid = false;
try
{
using (var tempReader = new MediaFoundationReader(file))
{
DefaultDecompressionFormat = tempReader.WaveFormat.ToString();
InputPath = file;
isValid = true;
}
}
catch (Exception e)
{
}
return isValid;
}
public string DefaultDecompressionFormat
{
get => defaultDecompressionFormat;
set
{
defaultDecompressionFormat = value;
RaisePropertyChanged("DefaultDecompressionFormat");
}
}
public string SongName { get => songName; set
{
songName = value;
RaisePropertyChanged("SongName");
} }
public string InputPath
{
get => inputPath;
set
{
if (inputPath != value)
{
inputPath = value;
AddToHistory(value);
RaisePropertyChanged("InputPath");
}
}
}
private void AddToHistory(string value)
{
if (!inputPathHistory.Contains(value))
{
inputPathHistory.Add(value);
}
}
public void Stop()
{
if (wavePlayer != null)
{
wavePlayer.Stop();
}
}
public void Pause()
{
if (wavePlayer != null)
{
wavePlayer.Pause();
RaisePropertyChanged("IsPlaying");
RaisePropertyChanged("IsStopped");
}
}
public void Play()
{
if (String.IsNullOrEmpty(InputPath))
{
return;
}
if (wavePlayer == null)
{
CreatePlayer();
}
if (lastPlayed != inputPath && reader != null)
{
reader.Dispose();
reader = null;
}
if (reader == null)
{
reader = new MediaFoundationReader(inputPath);
lastPlayed = inputPath;
wavePlayer.Init(reader);
}
wavePlayer.Play();
RaisePropertyChanged("IsPlaying");
RaisePropertyChanged("IsStopped");
timer.Start();
}
private void CreatePlayer()
{
wavePlayer = new WaveOutEvent();
wavePlayer.PlaybackStopped += WavePlayerOnPlaybackStopped;
RaisePropertyChanged("wavePlayer");
}
private void WavePlayerOnPlaybackStopped(object sender, StoppedEventArgs stoppedEventArgs)
{
if (reader != null)
{
SliderPosition = 0;
//reader.Position = 0;
timer.Stop();
}
if (stoppedEventArgs.Exception != null)
{
}
RaisePropertyChanged("IsPlaying");
RaisePropertyChanged("IsStopped");
}
public void PlayFromUrl(string url, string songname)
{
Stop();
inputPath = url;
SongName = songname;
Play();
}
public void Dispose()
{
wavePlayer?.Dispose();
reader?.Dispose();
}
}
XAML of player:
<Grid>
<StackPanel Orientation="Horizontal">
<Button Content="Play" Command="{Binding PlayCommand}" VerticalAlignment="Center" Width="75" />
<Button Content="Pause" Command="{Binding PauseCommand}" VerticalAlignment="Center" Width="75" />
<Button Content="Stop" Command="{Binding PlayCommand}" VerticalAlignment="Center" Width="75" />
<Slider VerticalAlignment="Center" Value="{Binding SliderPosition, Mode=TwoWay}" Maximum="10" Width="400" />
<TextBlock Text="{Binding SongName, FallbackValue=Test}" Foreground="White"/>
</StackPanel>
</Grid>
</UserControl>
VM code that sends data for a new track:
public class AudioModel
{
public string Artist { get; set; }
public string SongName { get; set; }
public int Duration { get; set; }
public string URL { get; set; }
public RelayCommand PlayThisAudioCommand
{
get;
private set;
}
public AudioModel()
{
PlayThisAudioCommand = new RelayCommand(() => PlayThis());
}
private void PlayThis()
{
if (URL != null)
{
TestVM.AudioConrol.PlayFromUrl(URL, SongName);
}
else;
}
}
It looks like you may have a multi-threading issue with your timer. The sequence of events appears to be:
First Track
PlayFromUrl() calls Play() which starts the file playing, and starts the timer.
Slider updates as expected
Second Track
When you call PlayFromUrl() it:
Calls Stop() (which stops the wavePlayer, and stops the timer)
Calls Play() (which starts the wavePlayer, and starts the timer)
Then the wavePlayer.PlaybackStopped event is raised (due to your earlier call to wavePlayer.Stop()), which calls WavePlayerOnPlaybackStopped(), which stops the timer.
The important point here is the order that Play() and WavePlayerOnPlaybackStopped() are called. It's very likely that the events are happening in the order above - as the wavePlayer raises the PlaybackStopped event on another thread.
In short - that WavePlayerOnPlaybackStopped() is stopping your timer after Play() started it, which is why your slider isn't updating. Pressing Pause then Play will restart the timer, which is why the slider begins updating after pausing.
You could test this by temporarily commenting out the code in WavePlayerOnPlaybackStopped(), which should fix the issue - although your slider will not reset to zero when the track reaches the end or stops.
NOTE: The cause of the delay between calling wavePlayer.Stop() and the wavePlayer.PlaybackStopped event being raised is due to nAudio using a dedicated thread to handle playback. When you call Stop(), it must finish processing the current audio buffer before actually stopping - which in most cases will result in a delay of a few milliseconds.
You can see this in action in the WaveOutEvent's DoPlayback method: https://github.com/naudio/NAudio/blob/master/NAudio/Wave/WaveOutputs/WaveOutEvent.cs#L147

Xamarin.Forms ListView Load More

The Problem
What I want to achieve can you basically see here
so when the user scrolls to the end I want to load more, because my List is very large and I want to Maximize Performance.
I'm trying to achieve this as follows, in splitting the Main Collection with the Data so that i can set the ItemSource new when the User reaches the end.
What ive Implemented so far
public class ViewModel : BaseViewModel {
public ViewModel() {
Initialize();
}
public List<List<Usermodel>> SplitedUserLists { get; set; }
//Main List that im Binding to
public List<Usermodel> ItemSourceCollection { get; set; }
public int ChunkSize { get; set; }
#endregion
private async void Initialize() {
ItemSourceCollection = await LoadList();
// Splites the list (in this case the chunk Size is 5)
SplitedScoreLists = ItemSourceCollection.Split(GetChunkSize()));
ItemSourceCollection = SplitedScoreLists[0];
}
//Gets called from CodeBehind
public void ListViewItemAppearing(ItemVisibilityEventArgs e) {
//Bottom Hit!
if (e.Item == ItemSourceCollection[ItemSourceCollection.Count - 1]) {
if (ChunkSize >= SplitedScoreLists.Count) {
return;
}
foreach (var usermodel in SplitedScoreLists[ChunkSize].ToList()) {
ItemSourceCollection.Add(usermodel);
}
if (ChunkSize < SplitedScoreLists.Count) {
ChunkSize++;
}
}
}
}
Questions
The problem with my Implementation is that the List is actually longer than the Count of the original List due to duplicates.
Is this the right way to achieve something like this?
Am I actually increasing Performance with this? I need to cause the List is 1000+ entries.
There are nice tutorials on how to achieve this:
http://motzcod.es/post/107620279512/load-more-items-at-end-of-listview-in
https://github.com/jguibault/Xamarin-Forms-Infinite-Scroll
http://www.codenutz.com/lac09-xamarin-forms-infinite-scrolling-listview/
The key point is when to raise the "load more" command:
public class InfiniteListView : ListView
{
public static readonly BindableProperty LoadMoreCommandProperty = BindableProperty.Create<InfiniteListView, ICommand>(bp => bp.LoadMoreCommand, default(ICommand));
public ICommand LoadMoreCommand
{
get { return (ICommand) GetValue(LoadMoreCommandProperty); }
set { SetValue(LoadMoreCommandProperty, value); }
}
public InfiniteListView()
{
ItemAppearing += InfiniteListView_ItemAppearing;
}
void InfiniteListView_ItemAppearing(object sender, ItemVisibilityEventArgs e)
{
var items = ItemsSource as IList;
if (items != null && e.Item == items[items.Count - 1])
{
if(LoadMoreCommand != null && LoadMoreCommand.CanExecute(null))
LoadMoreCommand.Execute(null);
}
}
}

Setting `VirtualizingPanel.IsVirtualizing` with TestStack.White

When testing virtualized panels I need to set the VirtualizingPanel.IsVirtualizing Property so that Teststack.White can interact with them like with non virtualized panels.
This helps me especially when panels have a lot of content.
I do not want to set VirtualizingPanel.IsVirtualizing statically so I do not have to deliver it like that to my customers.
To play around with a minimal example you will need a window.
<Window x:Class="DataGridTest.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:DataGridTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<DataGrid
AutomationProperties.AutomationId="MyDataGRID"
ItemsSource="{Binding MyItems}"
VirtualizingPanel.IsVirtualizing="True" >
<!-->
"IsVirtualizing Defaults to True."
"Setting this to False makes the test pass but is a poor choice for production code."
"Somehow I need to be able to change this programatically during testing."
</!-->
</DataGrid>
</Window>
Code behind for the window above.
using System.Collections.Generic;
using System.Windows;
namespace DataGridTest
{
public class Item
{
private string str;
public Item(string str) { this.str = str; }
public string Value { get { return str; } }
public int Length { get { return str.Length; } }
public int Hash { get { return str.GetHashCode(); } }
}
public partial class MainWindow : Window
{
List<Item> myitems;
public List<Item> MyItems { get { return myitems; } }
public MainWindow()
{
InitializeComponent();
myitems = new List<Item>();
for (int i = 0; i < 800; ++i)
{
myitems.Add(new Item($"Item {i}"));
}
DataContext = this;
}
}
}
And finally a Testing project:
using NUnit.Framework;
using System.Diagnostics;
using TestStack.White;
using TestStack.White.UIItems;
using TestStack.White.UIItems.WindowItems;
namespace NunitTest
{
[TestFixture]
public class Class1
{
private Application app;
private Window window;
[OneTimeSetUp]
public void OneTimeSetUp()
{
ProcessStartInfo info = new ProcessStartInfo( $"{TestContext.CurrentContext.WorkDirectory}/DataGridTest.exe");
info.WorkingDirectory = TestContext.CurrentContext.WorkDirectory;
app = Application.Launch(info);
window = app.GetWindow("MainWindow");
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
window.Close(); window = null;
app.Close(); app = null;
}
[Test]
public void test()
{
ListView list = window.Get<ListView>("MyDataGRID");
SetIsVirtualizing(list, false);
Assert.AreEqual(800, list.Rows.Count, "This fails for virtualized panels");
SetIsVirtualizing(list, true);
}
private void SetIsVirtualizing(ListView list, bool value)
{
//insert magic - I tried a couple of things but I just can not set this dependency property
}
}
}
Please help be to understand how VirtualizingPanel.IsVirtualizing can be set during testing.
I had some success with adding a collapsed textbox to interact with the datacontext. Although I am very unhappy about that solution it does pass the testing.
Here are modifications to the code that I made:
window
<StackPanel>
<TextBox
AutomationProperties.AutomationId="MyItems_IsVirtualizing_Injector"
Text="{Binding MyItems_IsVirtualizing_Injector}" Visibility="Collapsed"/>
<DataGrid
AutomationProperties.AutomationId="MyDataGRID"
ItemsSource="{Binding MyItems}"
VirtualizingPanel.IsVirtualizing ="{Binding MyItems_IsVirtualizing}"
>
<!-->
"IsVirtualizing Defaults to True."
"Setting this to False makes the test pass but is a poor choice for production code."
"Somehow I need to be able to change this programatically during testing."
</!-->
</DataGrid>
</StackPanel>
code behind
string injector = true.ToString();
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string MyItems_IsVirtualizing_Injector
{
get { return injector; }
set
{
injector = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyItems_IsVirtualizing_Injector"));
PropertyChanged(this, new PropertyChangedEventArgs("MyItems_IsVirtualizing"));
}
}
public bool MyItems_IsVirtualizing { get { return string.Equals(true.ToString(), MyItems_IsVirtualizing_Injector); } }
testing
private void SetIsVirtualizing(ListView list, bool value)
{
var injector = window.Get<TextBox>("MyItems_IsVirtualizing_Injector");
injector.Text = value.ToString();
}
EDIT: Since my actual usecase is counting the elements I actually settled for another solution that can be called using CountElements(list.AutomationElement)
private static int CountElements(AutomationElement container)
{
var patterns = container.GetSupportedPatterns();
var itemContainer = container.GetCurrentPattern(ItemContainerPattern.Pattern) as ItemContainerPattern;
List<object> elements = new List<object>();
var element = itemContainer.FindItemByProperty(null, null, null);
while (element != null)
{
elements.Add(element);
element = itemContainer.FindItemByProperty(element, null, null);
}
return elements.Count;
}

Calculating total of a ListView column WPF C#

I'm creating an EPOS system for a bar, for my own project just to test my skills.
I've come into a problem, I've managed to put all products in a WrapPanel and when clicked ive also managed to get them to show in a ListView control.
However, I cannot seem to get the total to show in a label below the ListView, essentially, each time a product is added to the ListView i want the total to also be updated by adding up all of the prices in the "Price" column and displaying them in a label below. But i cannot even seem to print the total via a button let alone do it automatically.
Here is my code for the button so far.
Don't suggest SubItems as it doesnt work in WPF.
private void Button_Click_1(object sender, RoutedEventArgs e) {
decimal total = 0;
foreach (ListViewItem o in orderDetailsListView.Items)
{
total = total + (decimal)(orderDetailsListView.SelectedItems[1]);
}
totalOutputLabel.Content = total;
}
I wrote this below answer to another question of yours on this same program that you deleted before I could post it. It covers updating the price but it also covers a lot more (using information that was in the deleted question).
First of all, if you want the screen to update when the items in the list are updated you must make the class implement INotifyPropertyChanged
public class OrderDetailsListItem : INotifyPropertyChanged
{
private string _name;
private decimal _price;
private int _quantity;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public decimal Price
{
get { return _price; }
set
{
if (value == _price) return;
_price = value;
OnPropertyChanged();
}
}
public int Quantity
{
get { return _quantity; }
set
{
if (value == _quantity) return;
_quantity = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now when the Price or Quantity is changed it will let the bindings know that the item was changed.
Next the reason that your if (OrderItem.Contains( caused duplicate items to show up is you must implement Equals( (and preferably GetHashCode()) for things like Contains( to work.
public class OrderDetailsListItem : INotifyPropertyChanged, IEquatable<OrderDetailsListItem>
{
//(Snip everything from the first example)
public bool Equals(OrderDetailsListItem other)
{
if (ReferenceEquals(null, other)) return false;
return string.Equals(_name, other._name);
}
public override bool Equals(object obj)
{
return Equals(obj as OrderDetailsListItem);
}
public override int GetHashCode()
{
return (_name != null ? _name.GetHashCode() : 0);
}
}
Another point, don't do OrderItem.CollectionChanged += in your button click, you will be creating extra event calls every collection changed event. Just set it one in the constructor and that is the only even subscription you need. However, there is a even better collection to use, BindingList<T> and its ListChanged event. A BindingList will raise the ListChange event in all the situations that ObserveableCollection raises CollectionChanged but in addition it will also raise the event when any item in the collection raises the INotifyPropertyChanged event.
public MainWindow()
{
_orderItem = new BindingList<OrderDetailsListItem>();
_orderItem.ListChanged += OrderItemListChanged;
InitializeComponent();
GetBeerInfo();
//You will see why all the the rest of the items were removed in the next part.
}
private void OrderItemListChanged(object sender, ListChangedEventArgs e)
{
TotalPrice = OrderItem.Select(x => x.Price).Sum();
}
Lastly, I would bet you came from a Winforms background. WPF is based around binding a lot more than winforms, I used to write code a lot like what you are doing before I really took that point in. All of those assignments to labels and collections should be done in the XAML with bindings, this allows for things like the INotifyPropertyChanged events to automatically update the screen without needing a function call.
Here is a simple recreation of your program that runs and uses bindings and all of the other things I talked about.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myNamespace ="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Content="{x:Static myNamespace:GlobalVariables._amstelProductName}" Click="amstelBeerButton_Click"/>
<TextBlock Text="{Binding TotalPrice, StringFormat=Total: {0:c}}"/>
</StackPanel>
<ListView Grid.Column="1" ItemsSource="{Binding OrderItem}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Name}" Header="Name"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Price, StringFormat=c}" Header="Price"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Quantity, StringFormat=N0}" Header="Quantity"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static readonly DependencyProperty TotalPriceProperty = DependencyProperty.Register(
"TotalPrice", typeof (decimal), typeof (MainWindow), new PropertyMetadata(default(decimal)));
private readonly BindingList<OrderDetailsListItem> _orderItem;
public MainWindow()
{
_orderItem = new BindingList<OrderDetailsListItem>();
_orderItem.ListChanged += OrderItemListChanged;
InitializeComponent();
DataContext = this;
GetBeerInfo();
}
public BindingList<OrderDetailsListItem> OrderItem
{
get { return _orderItem; }
}
public decimal TotalPrice
{
get { return (decimal) GetValue(TotalPriceProperty); }
set { SetValue(TotalPriceProperty, value); }
}
private void GetBeerInfo()
{
OrderItem.Add(new OrderDetailsListItem
{
Name = "Some other beer",
Price = 2m,
Quantity = 1
});
}
private void OrderItemListChanged(object sender, ListChangedEventArgs e)
{
TotalPrice = _orderItem.Select(x => x.Price).Sum();
}
private void amstelBeerButton_Click(object sender, RoutedEventArgs e)
{
//This variable makes me suspicous, this probibly should be a property in the class.
var quantityItem = GlobalVariables.quantityChosen;
if (quantityItem == 0)
{
quantityItem = 1;
}
var item = OrderItem.FirstOrDefault(i => i.Name == GlobalVariables._amstelProductName);
if (item == null)
{
OrderItem.Add(new OrderDetailsListItem
{
Name = GlobalVariables._amstelProductName,
Quantity = quantityItem,
Price = GlobalVariables._amstelPrice
});
}
else if (item != null)
{
item.Quantity = item.Quantity + quantityItem;
item.Price = item.Price*item.Quantity;
}
//The UpdatePrice function is nolonger needed now that it is a bound property.
}
}
public class GlobalVariables
{
public static int quantityChosen = 0;
public static string _amstelProductName = "Amstel Beer";
public static decimal _amstelPrice = 5;
}
public class OrderDetailsListItem : INotifyPropertyChanged, IEquatable<OrderDetailsListItem>
{
private string _name;
private decimal _price;
private int _quantity;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public decimal Price
{
get { return _price; }
set
{
if (value == _price) return;
_price = value;
OnPropertyChanged();
}
}
public int Quantity
{
get { return _quantity; }
set
{
if (value == _quantity) return;
_quantity = value;
OnPropertyChanged();
}
}
public bool Equals(OrderDetailsListItem other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(_name, other._name);
}
public event PropertyChangedEventHandler PropertyChanged;
public override bool Equals(object obj)
{
return Equals(obj as OrderDetailsListItem);
}
public override int GetHashCode()
{
return (_name != null ? _name.GetHashCode() : 0);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Just did a test on this, you should make sure you are getting into the event handler by adding a breakpoint. If you are not, make sure you have registered the handler to the click event, e.g.:
<Button Name="TestButton" Click="Button_Click_1"/>
If you are playing around with WPF I would strongly suggest looking at MVVM and data binding at some point.

Categories