I'm trying to create multiple sliders but each slider only changes the last object to be created
XML
<DataTemplate x:Key = "processTemplate">
<StackPanel Orientation = "Horizontal">
<TextBlock Text = "{Binding Path = Name, Mode = TwoWay}" Width = "100" Margin = "3 5 3 5"/>
<TextBlock Text = "{Binding Path = ID, Mode = TwoWay}" Width = "100" Margin = "0 5 3 5"/>
<Slider Value="{Binding Path = Volume, Mode = TwoWay}" Maximum="100" Width="100"/>
</StackPanel>
</DataTemplate>
CLASS
public class ProcessList
{
private string name;
public string Name { get; set; }
private int id;
public int ID { get; set; }
private float volume;
public float Volume {
get { return volume; }
set {
if (volume != value) {
volume = value;
SetApplicationVolume(this.id, volume);
MessageBox.Show(this.id.ToString());
}
}
}
}
and how it's populated
processes.Add(new ProcessList() { Name = theprocess.ProcessName, ID = theprocess.Id, Volume = VolumeMixer.GetApplicationVolume(theprocess.Id)});
How can I change it so each slider changes the volume of its process
If anyone is as stupid as me here is the corrected code:
private string name = "";
public string Name {
get {
return name;
}
set {
if (name != value) {
name = value;
NotifyPropertyChanged("Name");
}
}
}
private int id = 0;
public int ID {
get {
return id;
}
set {
if (id != value) {
id = value;
NotifyPropertyChanged("ID");
}
}
}
The ID property is not synchronized with its backing field id, and id is always the same 0.
Also, you should pass ID and Volume through the constructor, because your code rely on the assumption that one would always assign Volume after the ID.
Related
I have 3 model classes as follows (Vegetable, Fruit and ItemInBasket):
public class Vegetable
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Vegetable(string _Name)
{
this.Name = _Name;
}
}
public class Fruit
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Fruit(string _Name)
{
this.Name = _Name;
}
}
public class ItemInBasket
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
object fruitorvegetable;
public object FruitOrVegetable
{
get { return fruitorvegetable; }
set { fruitorvegetable = value; }
}
int quantity;
public int Quantity
{
get { return quantity; }
set { quantity = value; }
}
public ItemInBasket(string _Name, object _FruitOrVegetable, int _Quantity)
{
this.Name = _Name;
this.FruitOrVegetable = _FruitOrVegetable;
this.quantity = _Quantity;
}
}
My ViewModel is:
public class ViewModel
{
private ObservableCollection<object> _availableItems;
public ObservableCollection<object> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
private ObservableCollection<ItemInBasket> _itemsInBasket;
public ObservableCollection<ItemInBasket> ItemsInBasket
{
get { return _itemsInBasket; }
set { _itemsInBasket = value; }
}
public ViewModel()
{
_availableItems = new ObservableCollection<object>();
_itemsInBasket = new ObservableCollection<ItemInBasket>();
this.GenerateAvailableItems();
this.GenerateItemsInBasket();
}
private void GenerateAvailableItems()
{
_availableItems.Add(new Vegetable("Broccoli")); // index 0
_availableItems.Add(new Vegetable("Kale")); // index 1
_availableItems.Add(new Vegetable("Spinach")); // index 2
_availableItems.Add(new Vegetable("Carrots")); // index 3
_availableItems.Add(new Vegetable("Garlic")); // index 4
_availableItems.Add(new Fruit("Apple")); // index 5
_availableItems.Add(new Fruit("Orange")); // index 6
_availableItems.Add(new Fruit("Pear")); // index 7
_availableItems.Add(new Fruit("Cherry")); // index 8
_availableItems.Add(new Fruit("Grape")); // index 9
}
private void GenerateItemsInBasket()
{
_itemsInBasket.Add(new ItemInBasket("Apples",_availableItems[5],3));
_itemsInBasket.Add(new ItemInBasket("Kale", _availableItems[1], 10));
_itemsInBasket.Add(new ItemInBasket("Grape", _availableItems[9], 2));
_itemsInBasket.Add(new ItemInBasket("Carrots", _availableItems[3], 1));
}
}
I am trying to be able to modify the FruitOrVegetable inside of each ItemInBasket displayed in the datagrid but I have an issue with the data binding. I am using Syncfusion datagrid but I think it shouldnt affect anything.
<syncfusion:SfDataGrid AutoGenerateColumns="False" SelectionMode="Single"
AllowEditing="True" AllowDeleting="True" ItemsSource="{Binding ItemsInBasket,Source={StaticResource viewModel}}">
<syncfusion:SfDataGrid.Columns>
<syncfusion:GridTemplateColumn MappingName="FruitOrVegetable" HeaderText="Order">
<syncfusion:GridTemplateColumn.EditTemplate>
<DataTemplate>
<ComboBox IsEditable="True" DisplayMemberPath="Name" SelectedValuePath="Name"
Text="{Binding FruitOrVegetable.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AvailableItems, Source={StaticResource viewModel}}"/>
</DataTemplate>
</syncfusion:GridTemplateColumn.EditTemplate>
<syncfusion:GridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</syncfusion:GridTemplateColumn.CellTemplate>
</syncfusion:GridTemplateColumn>
<syncfusion:GridNumericColumn HeaderText="Quantity" MappingName ="Quantity"/>
</syncfusion:SfDataGrid.Columns>
</syncfusion:SfDataGrid>
Your requirement to display the modified value of the FruitOrVegetable in SfDataGrid can be achieved by binding the SelectedValue property of ComboBox. Please refer to the below code snippet,
<ComboBox IsEditable="True" DisplayMemberPath="Name" SelectedValuePath="Name"
SelectedValue="{Binding Name}"
Text="{Binding FruitOrVegetable.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AvailableItems, Source={StaticResource viewModel}}"/>
This is where what we call an "interface" comes in handy. This gives you the surety that the properties are indeed present on this object. Consider it a contract between the class and interface to always include some properties.
You can learn more about it here
You can define an interface like this
public interface INamedItem
{
string Name {get;set;}
}
Now instead of the object type use this interface(first implement them in your vegetable and fruit class) so that you can easily bind to it(and many other benefits)
public class Vegetable : INamedItem
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Vegetable(string _Name)
{
this.Name = _Name;
}
}
public class Fruit: INamedItem
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Fruit(string _Name)
{
this.Name = _Name;
}
}
public class ItemInBasket
{
string name;
public string Name
{
get { return name; }
set { name = value; }
}
INamedItem fruitorvegetable;
public object FruitOrVegetable
{
get { return fruitorvegetable; }
set { fruitorvegetable = value; }
}
int quantity;
public int Quantity
{
get { return quantity; }
set { quantity = value; }
}
public ItemInBasket(string _Name, INamedItem _FruitOrVegetable, int _Quantity)
{
this.Name = _Name;
this.FruitOrVegetable = _FruitOrVegetable;
this.quantity = _Quantity;
}
}
For your available items it becomes:
private ObservableCollection<INamedItem> _availableItems;
public ObservableCollection<INamedItem> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
Now add normally as you have in your code so no more changes to that and check if it works :)
I'm trying to create a UWP-app in C# that can control my lights in my home. I am able to fetch the data from the server and create lamp objects for each individual lamp. These lamp objects are then place in an ObservableCollection on the beginning of the app. This ObservableCollection is bound to a GridView with an DataTemplate. When the app started i can see my lights with the right data. I then refetch the data to check if any lamp property has changed every 500ms. I can clearly see that the object properties are succesfully updated, but the bound data doesn't recognize this change. So the UI does not change either. I tried to use the NotifyPropertyChange in Lamp class, but that did nothing either.
After a lot of trial and error i found that the ui only changes when I add, delete or replace an object in the ObservableCollection, but replacing is not really a practical option for me as it causes a lot of instabillity and does not look like that is the way this problem has to be resolved.
<GridView ItemsSource="{x:Bind LampCollection}" Margin="10 0" HorizontalAlignment="Center">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:Lamp">
<Border BorderBrush="#555555" BorderThickness="1" CornerRadius="8" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10" >
<Grid Width="300" Height="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Grid.Column="0" Source="{x:Bind ImageUri, Mode=OneWay}" Width="80" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" >
<TextBlock Name="txt" VerticalAlignment="Bottom" FontSize="20" FontWeight="Bold" Margin="10,0,0,20" Text="{x:Bind Name, Mode=OneTime}"/>
<TextBlock Name="status" VerticalAlignment="Bottom" FontSize="11" FontWeight="Bold" Margin="10,0,0,20" Text="{x:Bind Status, Mode=OneWay}"/>
</StackPanel>
<Rectangle Grid.Row="1" Grid.Column="0" Visibility="{x:Bind ColorLamp}" Width="50" Height="50" Fill="Maroon"/>
<Slider Visibility="{x:Bind Dimmable}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="10,0,10,0" Value="{x:Bind Brightness, Mode=TwoWay}"/>
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Xaml code
The lamp.SetStatus function just parses the string and sets the properties Brightness and Status which are bound to the UI.
foreach (Lamp lamp in LampCollection) {
string response = await GetAsync(UrlString + lamp.IDX.ToString());
dynamic json = JsonConvert.DeserializeObject(response);
if (json.status == "OK") {
lamp.SetStatus(json.result[0].Status.ToString());
}
}
C# update code
Edit
I tried to implement the INotifyPropertyChanged in my lamp class as described in Microsoft's documentation. It doesn't seem to do anything however. I also tried passing in the name in the NotifyPropertyChanged() function but that only made my app crash.
class Lamp : INotifyPropertyChanged {
public uint IDX { get; internal set; }
public string Name { get; internal set; }
public bool Status { get; internal set; }
public string ImageUri { get; internal set; }
public bool Dimmable { get; internal set; }
public bool ColorLamp { get; internal set; }
public uint Brightness { get; set; }
public float[] Color { get; set; }
public Lamp(uint idx, string name, string status, bool dimmable, bool colorLamp) {
IDX = idx;
Name = name;
Color = new float[3];
Dimmable = dimmable;
ColorLamp = colorLamp;
if (status == "Off") {
ImageUri = "Images/lamp-off.svg";
Status = false;
} else {
ImageUri = "Images/lamp-on.svg";
Status = true;
if(dimmable) {
Brightness = uint.Parse(Regex.Match(status, #"\d+").Value, NumberFormatInfo.InvariantInfo);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Switch(bool status) {
Status = status;
if(status) ImageUri = "Images/lamp-on.svg";
else ImageUri = "Images/lamp-off.svg";
NotifyPropertyChanged();
}
public void SetColor(float r, float g, float b) { if (ColorLamp) { Color[0] = r; Color[1] = g; Color[2] = b; } }
public void SetStatus(string status) {
if (status == "Off") {
if (Status) {
ImageUri = "Images/lamp-off.svg";
Status = false;
if (Dimmable) Brightness = 0;
Debug.WriteLine(Name + "(" + IDX + ") has turned off");
NotifyPropertyChanged();
}
} else {
if (Dimmable) {
uint _tmpBright = uint.Parse(Regex.Match(status, #"\d+").Value, NumberFormatInfo.InvariantInfo);
if(!Status || Brightness != _tmpBright) {
ImageUri = "Images/lamp-on.svg";
Status = true;
Brightness = _tmpBright;
Debug.WriteLine(Name + "(" + IDX + ") has turned on or changed brighntess");
NotifyPropertyChanged();
}
} else {
if (!Status) {
ImageUri = "Images/lamp-on.svg";
Status = true;
Debug.WriteLine(Name + "(" + IDX + ") has turned on");
NotifyPropertyChanged();
}
}
}
}
}
Based your code snippet, you called the NotifyPropertyChanged() method in the SetStatus() method and the CallerMemberName allows you to obtain the method or property name of the caller to the method, if you do not pass any propertyName to the NotifyPropertyChanged() method, it will automatically obtain the method name which is SetStatus. However, there is no UI bound with SetStatus, so the UI won't update. If you want to update the UI which bound with Status and Brightness properties in this scenario, you could pass these two property names to NotifyPropertyChanged() method, for example:
public void SetStatus(string status)
{
if (status == "Off")
{
if (Status)
{
ImageUri = "Assets/2.jpg";
Status = false;
if (Dimmable) Brightness = 0;
Debug.WriteLine(Name + "(" + IDX + ") has turned off");
NotifyPropertyChanged("Status");
NotifyPropertyChanged("Brightness");
}
}
......
}
However, every time when you change the values of Status and Brightness properties in SetStatus() method or other methods in your Lamp class, you need to call NotifyPropertyChanged("xxx") method, it is a little complicated. You can declare a private variable and override get and set methods, in the set method, calling the NotifyPropertyChanged() method, every time set a new value to your property, it will enter the set method and then notify the UI to update. Take Status and Brightness as examples:
public class Lamp : INotifyPropertyChanged
{
private bool status { get; set; }
private uint brightness { get; set; }
public bool Status {
get {
return status;
}
set {
status = value;
NotifyPropertyChanged();
}
}
public uint Brightness
{
get
{
return brightness;
}
set
{
brightness = value;
NotifyPropertyChanged();
}
}
// The same behavior to the following properties
public uint IDX { get; internal set; }
public string Name { get; internal set; }
public string ImageUri { get; internal set; }
public bool Dimmable { get; internal set; }
public bool ColorLamp { get; internal set; }
public float[] Color { get; set; }
public Lamp(uint idx, string name, string status, bool dimmable, bool colorLamp)
{
IDX = idx;
Name = name;
Color = new float[3];
Dimmable = dimmable;
ColorLamp = colorLamp;
if (status == "Off")
{
ImageUri = "Assets/2.jpg";
Status = false;
}
else
{
ImageUri = "Assets/3.jpg";
Status = true;
if (dimmable)
{
Brightness = uint.Parse(Regex.Match(status, #"\d+").Value, NumberFormatInfo.InvariantInfo);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Switch(bool status)
{
Status = status;
if (status) ImageUri = "Assets/3.jpg";
else ImageUri = "Assets/2.jpg";
}
public void SetColor(float r, float g, float b) { if (ColorLamp) { Color[0] = r; Color[1] = g; Color[2] = b; } }
public void SetStatus(string status)
{
if (status == "Off")
{
if (Status)
{
ImageUri = "Assets/2.jpg";
Status = false;
if (Dimmable) Brightness = 0;
}
}
else
{
if (Dimmable)
{
uint _tmpBright = 30;
if (!Status || Brightness != _tmpBright)
{
ImageUri = "Assets/3.jpg";
Status = true;
Brightness = _tmpBright;
}
}
else
{
if (!Status)
{
ImageUri = "Assets/3.jpg";
Status = true;
}
}
}
}
}
I've posted the same question before but it wasn't clear (and contained too many self-induced errors in attempt to fix the code) so re-posting it with more details.
So I have "MainUiWindow.xaml" file which uses databinding like this:
<ItemsControl x:Name="gridSettingsMonster" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding SettingsMonster}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type core:Setting}">
<Grid x:Name="gridMonster">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Label}" IsEnabled="{Binding Enabled}" ToolTip="{Binding Description}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="10 5 10 5" FontWeight="{Binding Fontweight}" ></TextBlock>
<ts:ToggleSwitch x:Name="toggleSwitchMonsterAll" IsEnabled="{Binding Enabled}" Grid.Row="0" Grid.Column="1" Command ="{Binding TriggerAction}" IsChecked="{Binding Value}" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5 0 20 2" Foreground="White" UncheckedText="" CheckedText="" UncheckedBorderBrush="#FF333333" CheckedBorderBrush="#FF2D2D30"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
SettingsMonster binding:
SettingsMonster.Add(new Setting(ConfigHelper.Main.Values.Overlay.MonsterWidget.IsVisible, true, "Monster_1", "Monster Widget", "Show/Hide Monsters Widget", new Command(_ =>
{
ConfigHelper.Main.Values.Overlay.MonsterWidget.IsVisible = !ConfigHelper.Main.Values.Overlay.MonsterWidget.IsVisible;
ConfigHelper.Main.Save();
})));
SettingsMonster.Add(new Setting(ConfigHelper.Main.Values.Overlay.MonsterWidget.ShowUnchangedMonsters, ConfigHelper.Main.Values.Overlay.MonsterWidget.IsVisible, "Monster_2", " Show unchanged monsters", "Automatically hide monsters if they are not damaged", new Command(_ =>
{
ConfigHelper.Main.Values.Overlay.MonsterWidget.ShowUnchangedMonsters = !ConfigHelper.Main.Values.Overlay.MonsterWidget.ShowUnchangedMonsters;
ConfigHelper.Main.Save();
})));
And finally, the Setting class:
public class Setting
{
public bool Value { get; set; }
public bool Enabled { get; set; }
public string Name { get; }
public string Label { get; }
public string Description { get; }
public string Checkbox_visibility { get; }
public string Fontweight { get; }
public List<Setting>SubSettings { get; }
public Command TriggerAction { get; }
public Setting(bool value, bool enabled, string name, string label, string description, Command action = null)
{
Value = value;
Enabled = enabled;
Name = name;
Label = label;
Description = description;
SubSettings = new List<Setting>();
TriggerAction = action;
}
}
Problem:
When I run the build and use the "ToggleSwitch" (it's basically a open-source checkbox) to change the value of "ConfigHelper.Main.Values.Overlay.MonsterWidget.IsVisible", it unchecks the UI correctly.
I want this checkbox to control the other checkboxes (i.e. "Monster_2") as well, so that when the main one is turned off, set IsEnabled value for the child checkboxes/textblocks to FALSE.
I got to the stage where if I check off the main one, restart the build, then the child checkboxes/textblocks are all set as IsEnabled=False. However, I want the same to happen in real time (i.e. refresh the UI without having to restart).
Any help would be appreciated.
EDIT 1.
So I have attempted implementing the INotifyPropertyChanged in my Settings class which looks like the following:
public class Setting : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _value;
private bool _enabled;
public bool Value
{
get
{
return _value;
}
set
{
if (_value == value)
return;
_value = value;
OnPropertyChanged(nameof(Enabled));
}
}
public bool Enabled
{
get
{
return _enabled;
}
set
{
if (_enabled == value)
return;
_enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public string Name { get; }
public string Label { get; }
public string Description { get; }
public string Checkbox_visibility { get; }
public string Fontweight { get; }
public List<Setting>SubSettings { get; }
public Command TriggerAction { get; }
public Setting(bool value, bool enabled, string name, string label, string description, Command action = null)
{
Value = value;
Enabled = enabled;
Name = name;
Label = label;
Description = description;
SubSettings = new List<Setting>();
TriggerAction = action;
}
But my UI is still not refreshing yet... any help?
My viewmodel was referencing incorrect variable in the first place.
I have implemented INotifyPropertyChange in my Setting object as below, and also added a command to be run when the checkbox is triggered.
public class Setting : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private bool _value;
private bool _enabled;
public bool Value
{
get
{
return _value;
}
set
{
if (_value == value)
return;
_value = value;
OnPropertyChanged(nameof(Value));
}
}
public bool Enabled
{
get
{
return _enabled;
}
set
{
if (_enabled == value)
return;
_enabled = value;
OnPropertyChanged(nameof(Enabled));
}
}
public string Name { get; }
public string Label { get; }
public string Description { get; }
public string Fontweight { get; }
public List<Setting>SubSettings { get; }
public Command TriggerAction { get; }
public Setting(bool _value, bool _enabled, string name, string label, string description, Command action = null)
{
Value = _value;
Enabled = _enabled;
Name = name;
Label = label;
Description = description;
SubSettings = new List<Setting>();
TriggerAction = action;
}
}
I'm trying to dynamically refresh the label that shows the current amount of space remaining but unfortunately the number doesn't refresh. Do you have any idea how to solve my problem?
C#
private void ReqDescText_Changed(object sender, TextChangedEventArgs e)
{
Counter ReqDescText_Counter = new Counter(ReqDescText, ReqDescLabelLength);
}
Class
public class Counter
{
public TextBox InputTextbox { get; set; }
public Label CounterLabel { get; set; }
public Counter(TextBox InputTextbox, Label CounterLabel)
{
int NB;
TextBox textBox = new TextBox();
var tempText = textBox.Text;
NB = (InputTextbox.MaxLength - tempText.Length);
CounterNumber counterNumber = new CounterNumber { Number = NB.ToString() };
CounterLabel.Content = counterNumber;
if (NB == 0)
{
CounterLabel.Foreground = new SolidColorBrush(Colors.Red);
}
}
class CounterNumber
{
public string Number { get; set; }
public override string ToString()
{
return "[" + Number + "]";
}
}
}
WPF
<Label x:Name ="ReqDescLabel" Content="Description" Grid.Row="1" Margin="5,5,0,5" Grid.Column="0"/>
<Label Name="ReqDescLabelLength" FontSize="10" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,6"/>
<TextBox x:Name ="ReqDescText" Padding="3" Grid.Row="1" Margin="0,5,0,5" Grid.Column="2" TextWrapping="Wrap" SpellCheck.IsEnabled="True" MaxLength="250" TextChanged="ReqDescText_Changed" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True"/>
How it's looks like
Problem solved. My mistake.
I did't take the current length of the text.
public class Counter
{
public TextBox InputTextbox { get; set; }
public Label CounterLabel { get; set; }
public Counter(TextBox InputTextbox, Label CounterLabel)
{
int NB;
var tempText = InputTextbox.Text;
NB = (InputTextbox.MaxLength - tempText.Length);
CounterNumber counterNumber = new CounterNumber { Number = NB.ToString() };
CounterLabel.Content = counterNumber;
if (NB == 0)
{
CounterLabel.Foreground = new SolidColorBrush(Colors.Red);
}
}
class CounterNumber
{
public string Number { get; set; }
public override string ToString()
{
return "[" + Number + "]";
}
}
}
I'm creating a form that will allows user to add filters to data for processing.
I have setup:
public Class RuleGroup
{
public ObservableCollection<Rule> Rules {get; set;}
...
}
public Class Rule
{
public ObservableCollection<String> Fields {get; set;}
public ObservableCollection<Rule> Rules {get; set;}
...
}
public class Criteria
{
public int ItemId{ get; set;}
public string Field{ get; set;}
public OperationType Operation{ get; set;}
public string Value {get; set;}
public string Value2 {get; set;}
}
So a Rule has a List of Criteria that must be matched if the rule is to be applied. Each Criteria in a Rule must specify a value for every field selected. The Amount of fields may vary from One RuleGroup to the next.
I am trying to set up a form that is user friendly when creating multiple Rules. I was thinking of having a GridView on the form that is some how bound to this class layout.
Rule = Row
Criteria = Column
Currently I have function that generates a DataTable based on the Rules/Criteria as the user move from one RuleGroup to the next, but I think there my be an nicer solution to this
Any ideas or help would be much appreciated
Thanks
Right Think I have got it.
Needed to change my Classes around a bit to get the correct groups / hierarchy. I have then been able to bind the column using the items index in the collection.
This has given me the outcome I wanted, Though there is a minor issue where I would like to be able to access the index using the string Name rather then the position. I am currently having to make sure that the "Criterion" are in the correct order when accessing the values.
Here is a link to the Source code
Rule Group
public class RuleGroup
{
public String Description { get; set; }
public ObservableCollection<Rule> Rules { get; set; }
public RuleGroup()
{
Rules = new ObservableCollection<Rule>();
}
}
Rule
public class Rule
{
public Rule()
{
Criteria = new ObservableCollection<Criteria>();
}
public String Description { get; set; }
public ObservableCollection<Criteria> Criteria { get; set; }
readonly ObservableCollection<RuleField> _Fields = new ObservableCollection<RuleField>();
public IEnumerable<RuleField> Fields
{
get
{
return _Fields;
}
}
public void AddField(string name, string header)
{
if (_Fields.FirstOrDefault(i => i.Name == name) == null)
{
RuleField field = new RuleField(_Fields.Count)
{
Name = name,
Header = header
};
_Fields.Add(field);
AddFieldToCriteria(field);
}
}
void AddFieldToCriteria(RuleField field)
{
foreach (Criteria c in Criteria)
{
if (c.Values.FirstOrDefault(i => i.Field == field) == null)
c.Values.Add(new Criterion() { Field = field, Operation = 1 });
}
}
}
Criteria
public class Criteria
{
public Criteria()
{
Values = new ObservableCollection<Criterion>();
}
public ObservableCollection<Criterion> Values { get; set; }
public Criterion this[int index]
{
get
{
return Values.OrderBy(i=>i.Field.Position).ElementAt(index);
}
set
{
Criterion c = Values.OrderBy(i => i.Field.Position).ElementAt(index);
c= value;
}
}
}
Criterion
public class Criterion
{
public RuleField Field { get; set; }
public int Operation { get; set; }
public object Value { get; set; }
public object Value2 { get; set; }
}
RuleField
public class RuleField
{
public string Name { get; set; }
public string Header { get; set; }
int _Position = 0;
public int Position
{
get
{
return _Position;
}
}
public RuleField(int position)
{
_Position = position;
}
}
View Model
public delegate void UpdateColumnsEventHandler(object sender, UpdateColumnsEventArgs e);
public class UpdateColumnsEventArgs
{
public IEnumerable<RuleField> Columns { get; set; }
public UpdateColumnsEventArgs()
{
Columns = new List<RuleField>();
}
public UpdateColumnsEventArgs(IEnumerable<RuleField> columns)
{
Columns = columns;
}
}
public class MainWindowViewModel
{
public event UpdateColumnsEventHandler UpdateColumns;
public ObservableCollection<RuleGroup> RuleGroups { get; set; }
RuleGroup _SelectedRuleGroup = null;
public RuleGroup SelectedRuleGroup
{
get
{
return _SelectedRuleGroup;
}
set
{
if (_SelectedRuleGroup == value)
return;
_SelectedRuleGroup = value;
}
}
public Rule _SelectedRule = null;
public Rule SelectedRule
{
get
{
return _SelectedRule;
}
set
{
if (_SelectedRule == value)
return;
_SelectedRule = value;
if (UpdateColumns != null)
UpdateColumns(this, new UpdateColumnsEventArgs(_SelectedRule.Fields));
}
}
public MainWindowViewModel()
{
RuleGroups = new ObservableCollection<RuleGroup>();
RuleGroup rg = new RuleGroup();
rg.Description = "Rule Group A";
Rule r = new Rule();
r.Description = "Rule 1";
Random random = new Random();
int range = 10000;
for (int x = 0; x < 2000; x++)
{
Criteria c = new Criteria();
c.Values.Add(new Criterion()
{
Field = new RuleField(0)
{
Name = "A",
Header = "A Column"
},
Operation = 1,
Value = "X"
});
c.Values.Add(new Criterion()
{
Field = new RuleField(0)
{
Name = "B",
Header = "B Column"
},
Operation = 1,
Value = x % 10
});
r.Criteria.Add(c);
}
#region Fields
r.AddField("A", "A Column");
r.AddField("B", "B Column");
r.AddField("C", "C Column");
#endregion
rg.Rules.Add(r);
r = new Rule();
r.Description = "Rule 2";
for (int x = 0; x < 1750; x++)
{
r.Criteria.Add(new Criteria());
}
#region Fields
r.AddField("A", "A Column");
r.AddField("B", "B Column");
#endregion
rg.Rules.Add(r);
RuleGroups.Add(rg);
}
}
WPF Window
<Window x:Class="RuleMappingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:RuleMappingTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainWindowViewModel UpdateColumns="UpdateGridColumns"/>
</Window.DataContext>
<Grid Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding RuleGroups}" SelectedItem="{Binding SelectedRuleGroup}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Row="1" ItemsSource="{Binding SelectedRuleGroup.Rules}" SelectedItem="{Binding SelectedRule}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DataGrid x:Name="CriteriaGrid" Grid.Row="2" ItemsSource="{Binding SelectedRule.Criteria}" AutoGenerateColumns="False" >
</DataGrid>
</Grid>
</Window>
WPF Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void UpdateGridColumns(object sender, UpdateColumnsEventArgs e)
{
CriteriaGrid.Columns.Clear();
foreach (RuleField rf in e.Columns)
{
DataGridTextColumn c = new DataGridTextColumn();
c.Header = rf.Header;
Binding b = new Binding(String.Format("[{0}].Value", rf.Position));
CriteriaGrid.Columns.Add(c);
c.Binding = b;
}
}
}