I have class structures as mentioned below.
//On the design aspects, I know It may not be the advisable approach,
//but something of this kind is only required.
/// <summary>
/// Paper Class
/// </summary>
public class Paper
{
public string PaperName { get; set; }
public bool IsPending { get; set; }
}
/// <summary>
/// PaperChecking class, Individual papers will be processed here.
/// </summary>
public class PaperChecking
{
public static List<Paper> ListPapers { get; set; }
public static void AddPapers()
{
ListPapers = new List<Paper>();
ListPapers.Add(new Paper() { PaperName = "Paper1", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper2", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper3", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper4", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper5", IsPending = false });
}
public static bool IsCheckingPending
{
get
{
//List has items and it is not null, so intentionally removed the checks.
return ListPapers.Count(paper => paper.IsPending == true) > 0 ? true : false;
}
}
}
/// <summary>
/// This class will select papers for processing
/// </summary>
public class ChangePaperSetting
{
public void SelectPaper(string paperName)
{
//It can be assumed that Paper object will never be NULL
PaperChecking.ListPapers.FirstOrDefault(paper => paper.PaperName.Equals(paperName)).IsPending = true;
}
}
Now,
I want to use property PaperChecking.IsCheckingPending to display some controls in my WPF window. I have bound the same property with Visibility of my controls. When window loads for the first time behavior is expected because Collection is already there. But at run-time when I am changing the Pending status of Paper object as below :
ChangePaperSetting changePaperSetting = new ChangePaperSetting();
changePaperSetting.SelectPaper("Paper1");
changePaperSetting.SelectPaper("Paper2");
changePaperSetting.SelectPaper("Paper5");
In my collection, now I have papers which have IsPending as true. So now PaperChecking.IsCheckingPending will return TRUE and according to that my controls should be visible now.
In a normal object I could have implemented INotifyPropertyChanged , but in above case I do not have a Setter on the property. Is there any way of doing this or any other neat approach using same kind class structures.
//-------------------------------------------------------------------------------//
Update
As suggested by Josh, I tried something like this :
/// <summary>
/// Paper Class
/// </summary>
public class Paper : INotifyPropertyChanged
{
public string PaperName { get; set; }
private bool isPending;
public bool IsPending
{
get
{
return isPending;
}
set
{
if (isPending != value)
{
isPending = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsPending"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// PaperChecking class, Individual papers will be processed here.
/// </summary>
public class PaperChecking : Control
{
public static List<Paper> listOfPapers { get; set; }
public static bool IsCheckingPending
{
get
{
//List has items and it is not null, so intentionally removed the checks.
try
{
return listOfPapers.Count(paper => paper.IsPending == true) > 0 ? true : false;
}
catch (Exception ex) { return false; }
}
}
public static event PropertyChangedEventHandler PropertyChanged;
public static void PendingStatusChanged(object sender,PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsPending")
{
//If I keep it static, It given Null Reference Error
//and If I implement INotifyPropertyChanged interface
//in this Class, it gives compilation error because
//I am doing so in my Static property.
PropertyChanged(null,new PropertyChangedEventArgs("IsCheckingPending"));
}
}
}
/// <summary>
/// This class will select papers for processing
/// </summary>
public class ChangePaperSetting
{
public static void AddPapers()
{
var listOfPapers = new List<Paper>();
for (int i = 0; i < 5; i++)
{
var paper = new Paper() { PaperName = "Paper"+i.ToString(),
IsPending = false };
paper.PropertyChanged+=PaperChecking.PendingStatusChanged;
listOfPapers.Add(paper);
}
PaperChecking.listOfPapers = listOfPapers;
}
public void SelectPaper(string paperName)
{
//It can be assumed that Paper object will never be NULL
PaperChecking.listOfPapers.FirstOrDefault(paper => paper.PaperName.Equals(paperName)).IsPending = true;
}
}
Here is my XAML Code :
<Window xmlns:my="clr-namespace:LearningWpf" x:Class="LearningWpf.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window4" Height="300" Width="300"
>
<Window.Resources>
<my:PaperChecking x:Key="paperChecking"/>
<BooleanToVisibilityConverter x:Key="bvc" />
</Window.Resources>
<StackPanel>
<Button Name="btn1" Content="Button1" Height="20" Width="80" Click="btn1_Click"></Button>
<Button Name="btn2" Content="Button2" Height="20" Width="80" Click="btn2_Click"></Button>
<Button Name="btn3" Content="Button3" Height="20" Width="80"
Visibility="{Binding Source={StaticResource paperChecking},
Path=IsCheckingPending,
Converter={StaticResource bvc}}"></Button>
</StackPanel>
Here is my CodeBehind.cs
public partial class Window4 : Window
{
public Window4()
{
InitializeComponent();
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
ChangePaperSetting.AddPapers();
}
private void btn2_Click(object sender, RoutedEventArgs e)
{
var v = PaperChecking.listOfPapers.FirstOrDefault(paper =>
paper.PaperName == "Paper1");
v.IsPending = true;
}
}
But this code is giving error, righlty so because I am using Static variable without initializing it. If there is any correction or any other approach to achieve the same target. Your help is highly appreciated.
Since you are using CLR property, so its your duty to notify the UI that underlying binding property has changed which is only achievable by raising PropertyChanged event from your code.
First of all make the collection as ObservableCollection since it implements INotifyCollectionChanged and INotifyPropertyChanged. Hook the collection changed event with your collection and in handler simply raise the propertyChanged event for your property like this -
ObservableCollection<Paper> listOfPapers = new ObservableCollection<Paper>();
listOfPapers.CollectionChanged += new NotifyCollectionChangedEventHandler(listOfPapers_CollectionChanged);
void listOfPapers_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("IsCheckingPending");
}
With this approach you won't have to worry if you need to add items in the collection from method other than SelectPaper().
Other possible solution could have been use Dependency Property instead of plain CLR property that way you won't have to worry about raising the property changed explicitly.
http://msdn.microsoft.com/en-us/library/ms752914.aspx
Something like this, maybe?
private static bool _isCheckingPending;
public static bool IsCheckingPending
{
get
{
bool pending = ListPapers.Count(paper => paper.IsPending == true) > 0;
if (pending != _isCheckingPending)
{
PropertyChanged("IsCheckingPending");
_isCheckingPending = pending;
}
//List has items and it is not null, so intentionally removed the checks.
return _isCheckingPending;
}
}
The idea is that it remembers the result from last time, and if it differs from the result this time, raise the PropertyChanged event (and of course you will ave implemented INotifyPropertyChanged).
Off topic, but why are all the properties and functions within PaperChecking static?
To solve your problem though, add a new function to PaperChecking and implement INotifyPropertyChanged.
public void SelectPaper(string paperName)
{
var paper = ListPapers.FirstOrDefault(paper => paper.PaperName.Equals(paperName));
if (paper != null)
{
paper.IsPending = true;
PropertyChanged("IsCheckingPending");
}
}
I'm assuming you know how to implement INotifyPropertyChanged, and have your own way of raising the event. There is nothing that says you must raise the event from a setter of a Property.
Having your property getter cycle through your entire list of papers each time it is queried is very very inefficient. It can and will be called very very often. Pretty much with any mouse or keyboard event. You should try caching the value of the count each time the pending status is changed on a Paper. It will take a bit more work, but it might be worth doing.
Edit:
Actually Objects can be updated from multiple interfaces and they will not call same method, they have reference to PAPER object and will update property directly, instead of calling method in PaperChecking class
In that case, you should implement INotifyPropertyChanged on the Paper class, and then listen for those updates within PaperChecking.
public void PaperChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == 'IsPending') PropertyChanged("IsCheckingPending");
}
public void AddPapers()
{
ListPapers = new List<Paper>();
ListPapers.Add(new Paper() { PaperName = "Paper1", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper2", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper3", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper4", IsPending = false });
ListPapers.Add(new Paper() { PaperName = "Paper5", IsPending = false });
foreach(var paper in ListPapers)
{
paper.PropertyChanged += PaperChanged;
}
}
You also need to transform PaperChecking into a class that uses instance methods and properties rather than static ones. If you don't already know about MVVM, I suggest reading up on it. Essentially, what you'd do, is create an instance of PaperChecking, and set it as the DataSource in your code behind of the View. Then, in your XAML, you can simply bind like this:
<Button Name="btn3" Content="Button3" Height="20" Width="80"
Visibility="{Binding IsCheckingPending, Converter={StaticResource bvc}}" />
Static properties and methods are nearly always wrong when starting out with WPF. Know when you need to use them, and when you're trying to make things easier on yourself.
Related
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; }
}
I want to be able to bind a TextBox with the UpdateSourceTrigger set to LostFocus (the default) but to perform validation as the user types in the text. The best I can come up with is to handle the TextChanged event and call a validation method on the view model. I'm wondering if there's a better solution.
My view model listens to property changes in the model in order to update itself (including formatting). I don't want to bind with the UpdateSourceTrigger set to PropertyChanged because that causes the text to be formatted as soon as the user types (for example, the user might want to type "1.2" yet as soon as he/she types "1" the text changes to "1.0" because of the automatic formatting by the view model).
Elaborating on the comment I left, here's an example of how it can be done.
FYI, I used the nuget package MvvmLight for the plumbing.
MainWindow.xaml
<StackPanel>
<TextBox x:Name="myTextBox" Text="{Binding SomeNumberViewModel.IntermediateText, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" Width="100" Margin="5"/>
<Button Content="Hi" HorizontalAlignment="Center" Padding="5,15" Margin="5"/>
</StackPanel>
MainWindow.xaml.cs
public MainViewModel ViewModel
{
get
{
return this.DataContext as MainViewModel;
}
}
public MainWindow()
{
InitializeComponent();
this.myTextBox.LostFocus += MyTextBox_LostFocus;
}
private void MyTextBox_LostFocus(object sender, RoutedEventArgs e)
{
// If the IntermediateText has no validation errors, then update your model.
if (string.IsNullOrEmpty(this.ViewModel.SomeNumberViewModel[nameof(this.ViewModel.SomeNumberViewModel.IntermediateText)]))
{
// Update your model and it gets formatted result
this.ViewModel.SomeNumberViewModel.ModelValue = this.ViewModel.SomeNumberViewModel.IntermediateText;
// Then, update your IntermediateText to update the UI.
this.ViewModel.SomeNumberViewModel.IntermediateText = this.ViewModel.SomeNumberViewModel.ModelValue;
}
}
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private SomeNumberViewModel someNumberViewModel;
public string MyTitle { get => "Stack Overflow Question 65279367"; }
public SomeNumberViewModel SomeNumberViewModel
{
get
{
if (this.someNumberViewModel == null)
this.someNumberViewModel = new SomeNumberViewModel(new MyModel());
return this.someNumberViewModel;
}
}
}
SomeNumberViewModel.cs
public class SomeNumberViewModel : ViewModelBase, IDataErrorInfo
{
public SomeNumberViewModel(MyModel model)
{
this.Model = model;
}
private string intermediateText;
public string IntermediateText { get => this.intermediateText; set { this.intermediateText = value; RaisePropertyChanged(); } }
public string ModelValue
{
get => this.Model.SomeNumber.ToString("0.00");
set
{
try
{
this.Model.SomeNumber = Convert.ToDouble(value);
RaisePropertyChanged();
}
catch
{
}
}
}
public MyModel Model { get; private set; }
public string Error { get => null; }
public string this[string columnName]
{
get
{
switch (columnName)
{
case "IntermediateText":
if (!string.IsNullOrEmpty(this.IntermediateText) && FormatErrors(this.IntermediateText))
return "Format errors";
break;
}
return string.Empty;
}
}
/// <summary>
/// Only allow numbers to be \d+, or \d+\.\d+
/// For Example: 1, 1.0, 11.23, etc.
/// Anything else is a format violation.
/// </summary>
/// <param name="numberText"></param>
/// <returns></returns>
private bool FormatErrors(string numberText)
{
var valid = (Regex.IsMatch(numberText, #"^(\d+|\d+\.\d+)$"));
return !valid;
}
}
MyModel.cs
public class MyModel
{
public double SomeNumber { get; set; }
}
I've set label Content to some custom class:
<Label>
<local:SomeContent x:Name="SomeContent" some="abc" />
</Label>
This properly displays "abc" in a view. However I can't figure out how do I notify the Label that the content property have changed i.e. this:
SomeContent.some = "xyz";
Will not cause the label to update it's view.
I know I can set binding to label's Content property. I have already like 7 different, working methods to achieve automatic update. However I'm interested in this particular behavior because it will save me a ton of work in some scenarios i.e the requirements are:
Label content is always the same SomeContent instance, only it's properties are changed.
No label content binding. The label should take a content object and refresh whenever the content is modified.
Initial value of some property can be set in XAML
some property can be changed in code, causing label refresh.
Am I missing something, or it's not possible?
This is my current implementation of SomeContent:
public class SomeContent : DependencyObject, INotifyPropertyChanged {
public static readonly DependencyProperty someProperty =
DependencyProperty.Register(nameof(some), typeof(string),
typeof(SomeContent),
new PropertyMetadata("", onDPChange)
);
private static void onDPChange(DependencyObject d, DependencyPropertyChangedEventArgs e) {
//throw new NotImplementedException();
(d as SomeContent).some = e.NewValue as String;
}
public event PropertyChangedEventHandler PropertyChanged;
public string some {
get => (string)GetValue(someProperty);
set {
SetValue(someProperty, value);
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(some))
);
}
}
public override string ToString() => some;
}
I turns out it's not possible to do it without third side code. So I wrote a helper class to do it easy now.
Dynamic object
public class SomeContent : IChangeNotifer {
public event Action<object> MODIFIED;
private string _some;
public string some {
get => _some;
set {
_some = value;
MODIFIED?.Invoke(this);
}
}
public override string ToString() => some;
}
You can add it to xaml file and it will be updated automatically. Single additional step is to add UIReseter somewhere bellow the elements that suppose to be auto-updated but that is needed only one for multiple contents in a tree.
Usage
<Window x:Class="DependencyContentTest.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:DependencyContentTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<local:UIReseter />
<Label>
<local:SomeContent x:Name="SomeContent" some="abcd" />
</Label>
<Grid>
<Label>
<local:SomeContent x:Name="nested" some="nyest"/>
</Label>
</Grid>
</StackPanel>
</Window>
MainWindow code
public partial class MainWindow : Window {
private Timer t;
public MainWindow() {
InitializeComponent();
t = new Timer(onTimer, null, 5000, Timeout.Infinite);
MouseDown += (s,e) => { SomeContent.some = "iii"; };
}
private void onTimer(object state) {
Dispatcher.Invoke(() => {
SomeContent.some = "aaaa";
nested.some = "xxx";
});
}
}
And this is the helper class that handles the update
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using H = System.Windows.LogicalTreeHelper;
using FE = System.Windows.FrameworkElement;
using DO = System.Windows.DependencyObject;
using System.Reflection;
using System.Windows.Markup;
namespace DependencyContentTest
{
public interface IChangeNotifer {
/// <summary>Dispatched when this object was modified.</summary>
event Action<object> MODIFIED;
}
/// <summary>This element tracks nested <see cref="IChangeNotifer"/> descendant objects (in logical tree) of this object's parent element and resets a child in it's panel property.
/// Only static (XAML) objects are supported i.e. object added to the tree dynamically at runtime will not be tracked.</summary>
public class UIReseter : UIElement {
public int searchDepth { get; set; } = int.MaxValue;
protected override void OnVisualParentChanged(DO oldParent){
if (VisualParent is FE p) p.Loaded += (s, e) => bind(p);
}
private void bind(FE parent, int dl = 0) {
if (parent == null || dl > searchDepth) return;
var chs = H.GetChildren(parent);
foreach (object ch in chs) {
if (ch is UIReseter r && r != this) throw new Exception($#"There's overlapping ""{nameof(UIReseter)}"" instance in the tree. Use single global instance of check ""{nameof(UIReseter.searchDepth)}"" levels.");
if (ch is IChangeNotifer sc) trackObject(sc, parent);
else bind(ch as FE, ++dl);
}
}
private Dictionary<IChangeNotifer, Reseter> tracked = new Dictionary<IChangeNotifer, Reseter>();
private void trackObject(IChangeNotifer sc, FE parent) {
var cp = getContentProperty(parent);
if (cp == null) return;
var r = tracked.nev(sc, () => new Reseter {
child = sc,
parent = parent,
content = cp,
});
r.track();
}
private PropertyInfo getContentProperty(FE parent) {
var pt = parent.GetType();
var cp = parent.GetType().GetProperties(
BindingFlags.Public |
BindingFlags.Instance
).FirstOrDefault(i => Attribute.IsDefined(i,
typeof(ContentPropertyAttribute)));
return cp ?? pt.GetProperty("Content");
}
private class Reseter {
public DO parent;
public IChangeNotifer child;
public PropertyInfo content;
private bool isTracking = false;
/// <summary>Function called by <see cref="IChangeNotifer"/> on <see cref="IChangeNotifer.MODIFIED"/> event.</summary>
/// <param name="ch"></param>
public void reset(object ch) {
if(! isChildOf(child, parent)) return;
//TODO: Handle multi-child parents
content.SetValue(parent, null);
content.SetValue(parent, child);
}
public void track() {
if (isTracking) return;
child.MODIFIED += reset;
}
private bool isChildOf(IChangeNotifer ch, DO p) {
if(ch is DO dch) {
if (H.GetParent(dch) == p) return true;
child.MODIFIED -= reset; isTracking = false;
return false;
}
var chs = H.GetChildren(p);
foreach (var c in chs) if (c == ch) return true;
child.MODIFIED -= reset; isTracking = false;
return false;
}
}
}
public static class DictionaryExtension {
public static V nev<K,V>(this Dictionary<K,V> d, K k, Func<V> c) {
if (d.ContainsKey(k)) return d[k];
var v = c(); d.Add(k, v); return v;
}
}
}
It could be improved and it not fully tested but it works for current purposes.
Additional problem is that some elements like TextBox cry about not suppopring SomeContent, like it is so hard to use ToString()... but that is another story, and is not related to my question.
Updated answer:
I'd throw away implementing SomeContent as a Dependency property and use a UserControl instead:
<UserControl x:Class="WpfApp1.SomeContent"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock Text="{Binding some, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SomeContent}}}"/>
</Grid>
</UserControl>
Then in code behind:
/// <summary>
/// Interaction logic for SomeContent.xaml
/// </summary>
public partial class SomeContent : UserControl
{
public static readonly DependencyProperty someProperty =
DependencyProperty.Register(nameof(some), typeof(string),
typeof(SomeContent),
new PropertyMetadata("")
);
public string some
{
get => (string)GetValue(someProperty);
set => SetValue(someProperty, value);
}
public SomeContent()
{
InitializeComponent();
}
}
Next, implement a view model that implements INotifyPropertyChanged:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _somePropertyOnMyViewModel;
public string SomePropertyOnMyViewModel
{
get => _somePropertyOnMyViewModel;
set { _somePropertyOnMyViewModel = value; OnPropertyChanged(); }
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then create an instance of MyViewModel in your view and assign it to your view's DataContext:
public class MyView : Window
{
public MyView()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
Then, finally, in MyView use the markup I provided in my original answer:
<Label>
<local:SomeContent x:Name="SomeContent" some="{Binding
SomePropertyOnMyViewModel" />
</Label>
It's my first post here, so I hope I'm doing everything correct.
I'm using the .NET Framework 4 Client Profile.
I want to load data from a .doc file into my program and work with this information. This can take a lot of time since I need to run through the tables of the document and check what's inside. That is already working, the only problem here is the screen is freezing and you can't see if something is happening.
Also I know this would be faster and way easier in excel, but since this type of data is and was always stored in word-documents in our company I have to keep it like that.
So what I want to do is count all rows from the tables that I have to read, set this as my Maximum Value for the Progress-Bar and then after each row I would count the value + 1.
I have my load Button with the Command bound to LoadWordDocCmd and the progress bar:
<Button Name="btnLoadFile"
Content="Load" Height="23"
Command="{Binding LoadWordDocCmd}"
HorizontalAlignment="Right" Margin="0,22,129,0"
VerticalAlignment="Top" Width="50"
Visibility="{Binding VisModeAddNew}"
/>
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="574,52,0,0"
VerticalAlignment="Top" Width="306"
Name="prgBarAddNewLoadWord"
Minimum="0"
Maximum="{Binding AddNewProgressBarMaxVal, Mode=OneWay}"
Value="{Binding AddNewProgressBarValue, Mode=OneWay}"
Visibility="{Binding AddNewProgressBarVisible}"/>
Here is the RelayCommand:
/// <summary>
/// Relaycommand for Function loadWordDocument
/// </summary>
public RelayCommand LoadWordDocCmd
{
get
{
if (this.m_loadWordDocCmd == null)
{
this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
}
return m_loadWordDocCmd;
}
private set
{
this.m_loadWordDocCmd = value;
}
}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
What I already did was to work with a BackgroundWorker.
I was able to bind the Button-Command to a function that has a RelayCommand with the BackgroundWorker, but then I wasn't able to check the canExecute function anymore.
I used this to test the Progress-Bar, that was working :
xaml:
<Button ...
Command="{Binding Path=InstigateWorkCommand}"
/>
cs :
private BackgroundWorker worker;
private ICommand instigateWorkCommand;
public ProggressbarSampleViewModel()
{
this.instigateWorkCommand = new
RelayCommand(o => this.worker.RunWorkerAsync(), o => !this.worker.IsBusy);
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
}
But how can I get this to work with the canExecute ? Here is my function-Header:
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
Here is the Relay-Command Class:
public class RelayCommand : ICommand
{
private readonly Action<object> methodToExecute;
private readonly Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public RelayCommand(Action<object> execute)
: this(execute, null) { }
public RelayCommand(Action<object> methodToExecute, Func<object, bool> canExecute)
{
this.methodToExecute = methodToExecute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
// wird keine canExecute-Funktion übergeben, so liefert diese
// true zurück, ansonsten wird die custom canExecute-Funktion
// mit den übergebenen Parametern aufgerufen.
return canExecute == null ? true : canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
methodToExecute(parameter);
}
}
Thank you for your help and I hope I posted this question correct!
I hope I understand your issue correctly.
The basic rule for a GUI application is: don't use the GUI thread for (time-consuming) data processing. You have to perform this task on a background thread.
Since you're using .NET 4.0 Client Profile, the async/await feature is not available to you. That would be the easiest solution, however.
You can do this with a ThreadPool instead. The BackgroundWorker is not recommended anymore.
In your XAML, you're binding the ProgressBar.Value property to a AddNewProgressBarValue property, so I assume you have a view-model with that property already. You have to ensure that changing AddNewProgressBarValue will raise the PropertyChanged event. And the good news is, the WPF Binding Engine automatically marshals the property value transfer operation to the GUI thread, so you don't need to care about which thread is changing a property your progress bar is bound to.
So the solution might look like this (not a production code, just an idea!):
class ViewModel : INotifyPropertyChanged
{
private bool isProcessing;
public bool AddNewProgressBarVisible
{
get { return this.isProcessing; }
// SetProperty here is a PRISM-like helper to set the backing field value
// and to raise the PropertyChanged event when needed.
// You might be using something similar.
private set { this.SetProperty(ref this.isProcessing, value, "AddNewProgressBarVisible");
}
private int progressValue;
public int AddNewProgressBarValue
{
get { return this.progressValue; }
private set { this.SetProperty(ref this.progressValue, value, "AddNewProgressBarValue");
}
// This is your command handler
private void LoadWordDocument(object parameter)
{
if (this.isProcessing)
{
// don't allow multiple operations at the same time
return;
}
// indicate that we're staring an operation:
// AddNewProgressBarVisible will set isProcessing = true
this.AddNewProgressBarVisible = true;
this.AddNewProgressBarValue = 0;
// Notify the bound button, that it has to re-evaluate its state.
// Effectively, this disables the button.
this.LoadWordDocCmd.RaiseCanExecuteChanged();
// Run the processing on a background thread.
ThreadPool.QueueUserWorkItem(this.DoLoadWordDocument);
}
private void DoLoadWordDocument(object state)
{
// Do your document loading here,
// this method will run on a background thread.
// ...
// You can update the progress bar value directly:
this.AddNewProgressBarValue = 42; // ...estimate the value first
// When you're done, don't forget to enable the button.
this.AddNewProgressBarVisible = false;
// We have to marshal this to the GUI thread since your ICommand
// implementation doesn't do this automatically
Application.Current.Dispatcher.Invoke(() => this.LoadWordDocCmd.RaiseCanExecuteChanged());
}
// this is your command enabler method
private bool CanLoadWordDoc(object parameter)
{
// if we're already loading a document, the command should be disabled
return this.m_fileSelected && !this.isProcessing;
}
}
I think that your ProggressbarSampleViewModel code sample is ok. I tested it and it works.
I am assuming that you want to change LoadWordDocCmd to have the behavior of InstigateWorkCommand. If you put the code from ProgressbarSampleViewModel into your actual ViewModel, you should have no problem accessing loadWordDocument and canLoadWordDoc. In addition, as mm8 mentioned, in your DoWork method you need to call RaiseCanExecuteChanged or else WPF will not check the CanExecute method.
Your ViewModel should look like bellow. See comments in upper case.
private BackgroundWorker worker;
private RelayCommand instigateWorkCommand; //CHANGE HERE
bool isBusy = false; // ADD THIS
public ProggressbarSampleViewModel()
{
//CHANGE NEXT LINE
this.instigateWorkCommand = new RelayCommand(
o => this.worker.RunWorkerAsync(),
o => !isBusy && canLoadWordDoc(null));
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
//REMOVE
//this.worker.ProgressChanged += this.ProgressChanged;
}
public ICommand InstigateWorkCommand
{
get { return this.instigateWorkCommand; }
}
private int _currentProgress;
public int CurrentProgress
{
get { return this._currentProgress; }
private set
{
if (this._currentProgress != value)
{
this._currentProgress = value;
OnPropertyChanged("CurrentProgress");
}
}
}
//REMOVE
//private void ProgressChanged(object sender, ProgressChangedEventArgs e)
//{
// this.CurrentProgress = e.ProgressPercentage;
//}
private void DoWork(object sender, DoWorkEventArgs e)
{
//ADD NEXT LINES
isBusy = true;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
// do time-consuming work here, calling ReportProgress as and when you can
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(10);
_currentProgress = i;
OnPropertyChanged("CurrentProgress");
}
//ADD NEXT LINES
isBusy = false;
Application.Current.Dispatcher.BeginInvoke(
(Action)instigateWorkCommand.RaiseCanExecuteChanged);
}
bool m_fileSelected = true; //CHANGE TO SEE THE EFFECT
//REMOVE
//RelayCommand m_loadWordDocCmd;
///// <summary>
///// Relaycommand for Function loadWordDocument
///// </summary>
//public RelayCommand LoadWordDocCmd
//{
// get
// {
// if (this.m_loadWordDocCmd == null)
// {
// this.m_loadWordDocCmd = new RelayCommand(this.loadWordDocument, canLoadWordDoc);
// }
// return m_loadWordDocCmd;
// }
// private set
// {
// this.m_loadWordDocCmd = value;
// }
//}
/// <summary>
/// checks if the Word Document can be loaded
/// </summary>
/// <param name="parameter">not used</param>
/// <returns>if it could Execute, then true, else false</returns>
private bool canLoadWordDoc(object parameter)
{
bool ret = false;
if (this.m_fileSelected)
{
ret = true;
}
return ret;
}
/// <summary>
/// Function for Load Word Document
/// </summary>
/// <param name="parameter">not used</param>
private void loadWordDocument(object parameter)
{
}
Hope this helps.
Just stumbled upon propertyGrid and its awesome! However, i have one task that i cant find how to do with it:
I have a class which has a type. Based on type, it has different properties available. I keep it in one class (not multiple inherited classes) for simplicity sake (there are ten types but they mostly have the same properties).
For example, i have a class MapObject which can have string type equal "player" or "enemy". For "enemy", property "name" is used, but for "player" it is blank (not used).
When i select two objects, one of which is of type "player" and other of type "enemy", i want property "name" to only "count" for the "enemy". So, i want propertyGrid to show the name of the object that has type="enemy", and when it (name property) is changed in Grid, only assign it to the object of type "enemy".
Is this possible to do?
Depending on whose PropertyGrid you are using, toggling the Browsable attribute may do what you want. See my answer here for how to do that at runtime.
If, like me, you are using the Xceed PropertyGrid then only changing the Browsable attribute at runtime once the control has loaded doesn't do anything. Instead, I also had to modify the PropertyDefinitions collection. Here is an extension method for doing that:
/// <summary>
/// Show (or hide) a property within the property grid.
/// Note: if you want to initially hide the property, it may be
/// easiest to set the Browable attribute of the property to false.
/// </summary>
/// <param name="pg">The PropertyGrid on which to operate</param>
/// <param name="property">The name of the property to show or hide</param>
/// <param name="show">Set to true to show and false to hide</param>
public static void ShowProperty(this PropertyGrid pg, string property, bool show)
{
int foundAt = -1;
for (int i=0; i < pg.PropertyDefinitions.Count; ++i)
{
var prop = pg.PropertyDefinitions[i];
if (prop.Name == property)
{
foundAt = i;
break;
}
}
if (foundAt == -1)
{
if (show)
{
pg.PropertyDefinitions.Add(
new Xceed.Wpf.Toolkit.PropertyGrid.PropertyDefinition()
{
Name = property,
}
);
}
}
else
{
if (!show)
{
pg.PropertyDefinitions.RemoveAt(foundAt);
}
}
}
In case the above does not work for you, the following may work better and is simpler anyway. It also doesn't use deprecated properties like the code above did...
public static void ShowProperty(this PropertyGrid pg, string property, bool show)
{
for (int i = 0; i < pg.Properties.Count; ++i)
{
PropertyItem prop = pg.Properties[i] as PropertyItem;
if (prop.PropertyName == property)
{
prop.Visibility = show ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
break;
}
}
}
This is a design pattern known as the state pattern. It is pretty easy to implement and you do not need property grids. http://www.dofactory.com/Patterns/PatternState.aspx
I made it with my own custom attribute:
public class VisibilityAttribute : Attribute
{
public bool IsVisible { get; set; }
public VisibilityAttribute(bool isVisible)
{
IsVisible = isVisible;
}
}
Then my data model:
public abstract class BaseSettings: INotifyPropertyChanged
{
public void SetVisibilityProperty(string propertyName, bool isVisible)
{
var theDescriptor = TypeDescriptor.GetProperties(this.GetType())[propertyName];
var theDescriptorVisibilityAttribute = (VisibilityAttribute)theDescriptor.Attributes[typeof(VisibilityAttribute)];
if (theDescriptorVisibilityAttribute == null) return;
theDescriptorVisibilityAttribute.IsVisible = isVisible;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class ThrottleSettings : BaseSettings
{
private ThrottleType _throttleType = ThrottleType.SmartThrottle;
[PropertyOrder(1)]
public ThrottleType ThrottleType
{
get
{
return _throttleType;
}
set
{
if (value == ThrottleType.FullThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), true);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), false);
}
else if (value == ThrottleType.SmartThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), false);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), true);
}
else if (value == ThrottleType.NoThrottle)
{
SetVisibilityProperty(nameof(FullThrottleTimeInMilliseconds), false);
SetVisibilityProperty(nameof(ThumbnailThrottleTimeInMilliseconds), false);
}
_throttleType = value;
}
}
private int _fullThrottleTime = 100;
[PropertyOrder(2)]
[Visibility(false)]
[Description("Specifies full throttle time (in milliseconds).")]
public int FullThrottleTimeInMilliseconds
{
get
{
return _fullThrottleTime;
}
set
{
if (value < 0) return;
_fullThrottleTime = value;
}
}
private int _thumbnailThrottleTime = 0;
[PropertyOrder(3)]
[Visibility(true)]
[Description("Specifies thumbnail throttle time (in milliseconds).")]
public int ThumbnailThrottleTimeInMilliseconds
{
get
{
return _thumbnailThrottleTime;
}
set
{
if (value < 0) return;
_thumbnailThrottleTime = value;
}
}
}
Finally I subscribed to 'propertyGrid_PropertyValueChanged' event and call there my method:
private void _propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
RefreshVisibility(_propertyGrid.Properties);
}
Here is method itself:
private void RefreshVisibility(IList properties)
{
foreach (PropertyItem property in properties)
{
var visibilityAttribute = GetVisibilityAttribute(property);
if (visibilityAttribute != null)
{
if (visibilityAttribute.IsVisible)
property.Visibility = Visibility.Visible;
else
property.Visibility = Visibility.Collapsed;
}
RefreshVisibility(property.Properties);
}
}