Update ObservableCollecttion on objects property changed - silverlight

Hopefully someone can help me out with this. I am creating a Silverlight app which is used for editing images. A user has a project which contain layers which contain elements. (Elements are text and image elements).
I have a class which represents the project. It contains an ObservableCollection and each Layer has an ObservableCollection. Element is an abstract class. There is a TextElement and ImageElement class which inherit from Element.
My problem is the UI never gets updated when I change an element inside the collection. I am using INotifyPropertyChanged on all my properties and I am catching CollectionChanged on the collections but still no go. The CollectionChanged event for ObservableCollection never gets hit on an update of one of its elements.
This is the code I had originally had:
void Elements_CollectionChanged(
object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{ this.NotifyChange("Elements"); }
This is the binding:
<!-- Workspace -->
<ScrollViewer Grid.Column="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="5" DataContext="{Binding Project}">
<Canvas x:Name="Canvas" Background="LightGray" Width="{Binding Path=CanvasWidth}" Height="{Binding Path=CanvasHeight}">
<ItemsControl ItemsSource="{Binding Path=Layers, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=Elements, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="{Binding Path=BackgroundColor}"
Visibility="{Binding Path=Visible, Converter={StaticResource BoolConverter}}"
Opacity="{Binding Path=Opacity}"
Width="{Binding ElementName=Canvas, Path=DataContext.CanvasWidth}"
Height="{Binding ElementName=Canvas, Path=DataContext.CanvasHeight}">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<ContentControl Content="{Binding Converter={StaticResource ElementConverter}}"
Canvas.Top="{Binding Path=Top, Mode=TwoWay}"
Canvas.Left="{Binding Path=Left, Mode=TwoWay}"
Opacity="{Binding Path=Opacity}"
MouseLeftButtonDown="ContentControl_MouseLeftButtonDown">
<interactivity:Interaction.Behaviors>
<behaviors:DragInCanvasBehavior />
</interactivity:Interaction.Behaviors>
</ContentControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</ScrollViewer>
This is my converter:
public class LayerElementToFrameworkElementConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Element element = value as Element;
if (element is ImageEditor.Client.BLL.TextElement)
{
ImageEditor.Client.BLL.TextElement txt = element as ImageEditor.Client.BLL.TextElement;
return CreateTextBlock(txt);
}
else if (element is ImageElement)
{
ImageElement imgEle = element as ImageElement;
return CreateImage(imgEle);
}
else
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement frameworkElement = value as FrameworkElement;
if (frameworkElement is TextBlock)
{
TextBlock txtBlock = value as TextBlock;
return CreateTextElement(txtBlock);
}
else if (frameworkElement is Image)
{
Image img = value as Image;
return CreateImageElement(img);
}
else
return null;
}
private TextBlock CreateTextBlock(TextElement textElement)
{
TextBlock text = new TextBlock();
text.Text = textElement.Text;
text.Cursor = Cursors.Hand;
text.SetValue(Canvas.ZIndexProperty, textElement.Order);
ColorHexToBrushConverter converter = new ColorHexToBrushConverter();
SolidColorBrush brush = (SolidColorBrush)converter.Convert(textElement.Color, null, null, null);
text.Foreground = brush;
return text;
}
private TextElement CreateTextElement(TextBlock textBlock)
{
TextElement textElement = new TextElement();
textElement.Text = textBlock.Text;
textElement.Top = (double)textBlock.GetValue(Canvas.TopProperty);
textElement.Left = (double)textBlock.GetValue(Canvas.LeftProperty);
textElement.Order = (int)textBlock.GetValue(Canvas.ZIndexProperty);
ColorHexToBrushConverter converter = new ColorHexToBrushConverter();
textElement.Color = (string)converter.ConvertBack(textBlock.Foreground, null, null, null);
return textElement;
}
private Image CreateImage(ImageElement imageElement)
{
Image img = new Image();
img.Width = imageElement.Width;
img.Height = imageElement.Height;
img.Source = imageElement.Source;
img.Opacity = imageElement.Opacity;
img.Cursor = Cursors.Hand;
img.SetValue(Canvas.ZIndexProperty, imageElement.Order);
img.SetValue(Canvas.TopProperty, imageElement.Top);
img.SetValue(Canvas.LeftProperty, imageElement.Left);
return img;
}
private ImageElement CreateImageElement(Image image)
{
ImageElement imageElement = new ImageElement();
imageElement.Width = image.Width;
imageElement.Height = image.Height;
imageElement.Source = (BitmapImage)image.Source;
imageElement.Order = (int)image.GetValue(Canvas.ZIndexProperty);
return imageElement;
}
}
This is the class which contains to collection:
public class Layer : BaseBLL
{
private int numberOfElements;
#region Properties
public int Order
{
get { return this.GetValue<int>("Order"); }
set { this.SetValue<int>("Order", value); }
}
public string BackgroundColor
{
get { return this.GetValue<string>("BackgroundColor"); }
set { this.SetValue<string>("BackgroundColor", value); }
}
public double Opacity
{
get { return this.GetValue<double>("Opacity"); }
set { this.SetValue<double>("Opacity", value); }
}
public bool Visible
{
get { return this.GetValue<bool>("Visible"); }
set { this.SetValue<bool>("Visible", value); }
}
public ObservableCollection<Element> Elements { get; set; }
public Element SelectedElement
{
get { return this.GetValue<Element>("SelectedElement"); }
set { this.SetValue<Element>("SelectedElement", value); }
}
#endregion
#region Commands
public ICommand AddTextElementCommand { get; set; }
public ICommand AddImageElementCommand { get; set; }
public ICommand DeleteElementCommand { get; set; }
public ICommand SetSelectedElementCommand { get; set; }
#endregion
#region Methods
public Layer()
:this("Untitled", 0, "#ffffff", 1, true, new ObservableCollection<Element>())
{
}
public Layer(string name, int order, string backgroundColor, double opacity, bool visible, ObservableCollection<Element> elements)
: base(name)
{
this.Order = order;
this.BackgroundColor = backgroundColor;
this.Opacity = opacity;
this.Visible = visible;
this.Elements = elements;
this.Elements.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Elements_CollectionChanged);
this.AddTextElementCommand = new DelegateCommand(AddTextElement, CanAddTextElement);
this.AddImageElementCommand = new DelegateCommand(AddImageElement, CanAddImageElement);
this.DeleteElementCommand = new DelegateCommand(DeleteElement, CanDeleteElement);
}
private void Elements_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (Element element in e.NewItems)
element.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(element_PropertyChanged);
}
void element_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.NotifyChange("Elements");
}
private bool CanAddTextElement(object param) { return true; }
private void AddTextElement(object param)
{
TextElement text = new TextElement();
text.Order = this.numberOfElements;
this.Elements.Add(text);
numberOfElements++;
this.SelectedElement = text;
}
private bool CanAddImageElement(object param) { return true; }
private void AddImageElement(object param)
{
var dialog = new OpenFileDialog()
{
Filter = "Image Files (*.bmp;*.jpg;*.gif;*.png;)|*.bmp;*.jpg;*.gif;*.png;",
Multiselect = false
};
bool? userClickedOK = dialog.ShowDialog();
if (userClickedOK == true)
{
string fileName = dialog.File.Name;
FileStream stream = dialog.File.OpenRead();
var imageSource = new BitmapImage();
using (FileStream fileStream = stream)
{
imageSource.SetSource(fileStream);
byte[] data = new byte[fileStream.Length];
fileStream.Read(data, 0, data.Length);
fileStream.Flush();
fileStream.Close();
}
ImageElement img = new ImageElement();
img.Name = fileName;
img.Order = this.numberOfElements;
img.Source = imageSource;
img.Height = imageSource.PixelHeight;
img.Width = imageSource.PixelWidth;
this.Elements.Add(img);
this.numberOfElements++;
this.SelectedElement = img;
}
}
private bool CanDeleteElement(object param)
{
if (this.SelectedElement != null)
return true;
else
return false;
}
private void DeleteElement(object param)
{
throw new NotImplementedException();
}
#endregion
}
This is the code for the one of the Elements:
public class TextElement : Element
{
public string Text
{
get { return this.GetValue<string>("Text"); }
set { this.SetValue<string>("Text", value); }
}
public int FontSize
{
get { return this.GetValue<int>("FontSize"); }
set { this.SetValue<int>("FontSize", value); }
}
public bool Bold
{
get { return this.GetValue<bool>("Bold"); }
set { this.SetValue<bool>("Bold", value); }
}
public bool Italic
{
get { return this.GetValue<bool>("Italic"); }
set { this.SetValue<bool>("Italic", value); }
}
public string Color
{
get { return this.GetValue<string>("Color"); }
set { this.SetValue<string>("Color", value); }
}
public FontFamily Font
{
get { return this.GetValue<FontFamily>("Font"); }
set { this.SetValue<FontFamily>("Font", value); }
}
public TextElement()
: this("Untitled", 1, 5, 5, 0, 0, 0, "New text", 12, false, false, "#aaaaaa", new FontFamily("Arial"))
{
}
public TextElement(string name, double opacity, double top, double left, double rotateAngle, double centerX, double centerY,
string text, int fontSize, bool bold, bool italic, string color, FontFamily font)
: base(name, opacity, top, left, rotateAngle, centerX, centerY)
{
this.Text = text;
this.FontSize = fontSize;
this.Bold = bold;
this.Italic = italic;
this.Color = color;
this.Font = font;
}
}
If anyone can help I would be very grateful.

Do you fire the PropertyChanged event from inside SetValue?
Otherwise, you should do it for each property of Element.
public string Text
{
get { return this.GetValue<string>("Text"); }
set { this.SetValue<string>("Text", value); this.InvokePropertyChanged("Text"); }
}
private void InvokePropertyChanged( string propertyName )
{
if( this.PropertyChanged != null )
this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
}

Related

WPF - How to change content of ContentControl clicking on TreeViewItem?

I am working on a WPF project and I am facing a problem. I have searched for 2 days a solution but I have never found anything that could help me...
I have a TreeView with different data types inside and a ContentControl that shows views corresponding to these different data types. I want, when I click on a TreeViewItem that, depending on the data type contained in this TreeViewItem, it changes the view in the ContentControl.
I reached to execute different commands depending on the TreeviewItem selected but it never changes the view...
Does somebody have an answer or an idea that could help me ?
I use this for my TreeView :
<TreeView x:Name="networkTree" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="490" TreeViewItem.Selected="GetHeaderSelectedItem"/>
It executes this :
public class NetworkConfigViewModel : BindableBase
{
private IRegionManager regionManager;
private bool _showRDConf;
private bool _showNetworkConf;
private bool _showDeviceInfo;
public NetworkConfigViewModel(IRegionManager regionManager)
{
this.regionManager = regionManager;
regionManager.RegisterViewWithRegion("NetworkConfigInfoRegion", typeof(NetworkConfigInfoView));
regionManager.RegisterViewWithRegion("RDConfigurationRegion", typeof(RDConfigurationView));
regionManager.RegisterViewWithRegion("DeviceInfoRegion", typeof(DeviceInfoView));
ShowNetworkConfCommand = new DelegateCommand(NetworkConf);
ShowRDConfCommand = new DelegateCommand(RDConf);
ShowDeviceInfoCommand = new DelegateCommand(DevInfo);
_showNetworkConf = true;
_showRDConf = false;
_showDeviceInfo = false;
}
public bool ShowRDConf
{
get
{
return _showRDConf;
}
set
{
SetProperty(ref _showRDConf, value);
}
}
public bool ShowNetworkConf
{
get
{
return _showNetworkConf;
}
set
{
SetProperty(ref _showNetworkConf, value);
}
}
public bool ShowDeviceInfo
{
get
{
return _showDeviceInfo;
}
set
{
SetProperty(ref _showDeviceInfo, value);
}
}
public DelegateCommand ShowNetworkConfCommand { get; set; }
public DelegateCommand ShowRDConfCommand { get; set; }
public DelegateCommand ShowDeviceInfoCommand { get; set; }
private void NetworkConf()
{
ShowRDConf = false;
ShowDeviceInfo = false;
ShowNetworkConf = true;
System.Windows.Forms.MessageBox.Show("Commande ShowNetConf executée :\nShowNetworkConf="+ShowNetworkConf.ToString()+"\nShowRDConf="+ShowRDConf.ToString() + "\nShowDeviceInfo=" + ShowDeviceInfo.ToString());
}
private void RDConf()
{
ShowNetworkConf = false;
ShowDeviceInfo = false;
ShowRDConf = true;
System.Windows.Forms.MessageBox.Show("Commande ShowRConf executée :\nShowRDConf="+ShowRDConf.ToString()+"\nShowNetworkConf="+ShowNetworkConf.ToString() + "\nShowRDeviceInfo=" + ShowDeviceInfo.ToString());
}
private void DevInfo()
{
ShowNetworkConf = false;
ShowRDConf = false;
ShowDeviceInfo = true;
System.Windows.Forms.MessageBox.Show("Commande ShowDeviceInfo executée :\nShowDeviceInfo=" + ShowDeviceInfo.ToString() + "\nShowNetworkConf=" + ShowNetworkConf.ToString() + "\nShowRDConf=" + ShowRDConf.ToString());
}
public void ChangeNetworkView(TreeView tree)
{
TreeViewItem tvi = null;
tvi = tree.SelectedItem as TreeViewItem;
if (tvi != null)
{
if (tvi.Tag.ToString() == "Network")
{
ShowNetworkConfCommand.Execute();
}
else if(tvi.Tag.ToString()=="RD")
{
ShowRDConfCommand.Execute();
}
else if (tvi.Tag.ToString() == "VD")
{
ShowDeviceInfoCommand.Execute();
}
}
}
}
And for my ContentControl :
<ContentControl x:Name="RDView" x:FieldModifier="public" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Visibility="{Binding Path=ShowRDConf, Converter={StaticResource Convert}}" prism:RegionManager.RegionName="RDConfigurationRegion"/>
<ContentControl x:Name="NetworkView" x:FieldModifier="public" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Visibility="{Binding Path=ShowNetworkConf, Converter={StaticResource Convert}}" prism:RegionManager.RegionName="NetworkConfigInfoRegion"/>
<ContentControl x:Name="DeviceView" x:FieldModifier="public" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Visibility="{Binding Path=ShowDeviceInfo, Converter={StaticResource Convert}}" prism:RegionManager.RegionName="DeviceInfoRegionq"/>
Thank you by advance

wpf ListItem SelectedValue Object is always null

I have a simple ListBox and a TextBox as under.I want to display the selectedvalue property of Listbox in the textbox,but my ViewModel's selected object is always null.
What am i missing here?
My XAML
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding CurrentRec.Name,Mode=OneWay}" />
<ListBox x:Name="AllMatching" Width="{Binding ElementName=TxtMail,Path=Width}" Height="100" Canvas.Top="54" Canvas.Left="36" DisplayMemberPath="Name" SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedValue="Name" SelectedValuePath="Name" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
My ViewModel:
public class VM_Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public List<DM_Data> AllData;
public DM_Data CurrentRec;
public VM_Data()
{
LoadData();
}
public int ID
{
get { return p_ID; }
set
{
if (p_ID != value)
{
RaisePropertyChangedEvent("ID");
p_ID = value;
}
}
}
public double SP
{
get { return p_SP; }
set
{
if (p_SP != value)
{
RaisePropertyChangedEvent("SP");
p_SP = value;
}
}
}
public double CP
{
get { return p_CP; }
set
{
if (p_CP != value)
{
RaisePropertyChangedEvent("CP");
p_CP = value;
}
}
}
public string Name
{
get { return p_Name; }
set
{
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
}
}
private void LoadData()
{
AllData = new List<DM_Data>();
string[] strNames = "Jatinder;Shashvat;shashikala;shamsher;shahid;justin;jatin;jolly;ajay;ahan;vijay;suresh;namita;nisha;negar;zenith;zan;zen;zutshi;harish;hercules;harman;ramesh;shashank;mandeep;aman;amandeep;amarjit;asim;akshay;amol;ritesh;ritivik;riz;samana;samaira;bhagwandass;bhagwan;bhawna;bhavna".Split(';');
for(int i=0;i<=strNames.GetUpperBound(0);i++)
{
DM_Data NewRec = new DM_Data();
NewRec.CP = new Random().Next(200, 400);
NewRec.SP = new Random().Next(1, 10);
NewRec.ID = i + 1;
NewRec.Name = strNames[i];
AllData.Add(NewRec);
}
AllData = AllData.OrderBy(item => item.Name).ToList();
}
private void RaisePropertyChangedEvent(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
}
My DataModel
public class DM_Data
{
public int p_ID;
public double p_SP, p_CP;
public string p_Name;
public int ID
{
get { return p_ID; }
set { p_ID = value; }
}
public double SP
{
get { return p_SP; }
set { p_SP = value; }
}
public double CP
{
get { return p_CP; }
set { p_CP = value; }
}
public string Name
{
get { return p_Name; }
set { p_Name = value; }
}
MainWindow.Xaml.cs
public partial class MainWindow : Window
{
VM_Data ViewModel;
public MainWindow()
{
InitializeComponent();
ViewModel = new VM_Data();
this.DataContext = ViewModel;
AllMatching.ItemsSource = ViewModel.AllData;
}
private void cmdtest_Click(object sender, RoutedEventArgs e)
{
DM_Data crec = ViewModel.CurrentRec;
}
}
CurrentRec must be a property that raises the PropertyChanged event:
private DM_Data _currentRec;
public DM_Data CurrentRec
{
get { return _currentRec; }
set { _currentRec = value; RaisePropertyChangedEvent("CurrentRec"); }
}
In the code you have posted, it is a field and you cannot bind to fields:
public DM_Data CurrentRec;
You can't bind to fields! CurrentRec must be a property. At now it is a field.
Why do you set ItemsSource in code-behind? Set it in XAML.
You should call RaisePropertyChangedEvent after you've changed backing field, not before.
It is not right pattern for events raising: if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(Property)); } You need to save event delegate to variable first or use ?.Invoke.
Don't create new instances of Random on every iteration of loop because you will get equal values. Create the only one outside of the loop and use it.
What you are doing is kind of MVVM, but not really.
Here is a quick fix anyway:
Please take a look at the Bindings.
<StackPanel>
<Canvas>
<TextBox x:Name="TxtMail" Width="244" FontSize="14" Canvas.Left="36" Canvas.Top="34" Height="20" Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}" />
<ListBox x:Name="AllMatching"
Width="{Binding ElementName=TxtMail,Path=Width}"
Height="100"
Canvas.Top="54"
Canvas.Left="36"
DisplayMemberPath="Name"
SelectedItem="{Binding CurrentRec,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto" />
<Button Content="Test" x:Name="cmdtest" Click="cmdtest_Click"/>
</Canvas>
</StackPanel>
I think you get the idea at: Text="{Binding ElementName=AllMatching, Path=SelectedItem.Name}".
Aditional Information
First:
You fire to early dude. Please first assign the value and then say its changed.
if (p_Name != value)
{
RaisePropertyChangedEvent("Name");
p_Name = value;
}
Second:
Use a ObservableCollection<DM_Data> to let your ListBox know about changes.
Third:
Use the posibility of Binding
Remove AllMatching.ItemsSource = ViewModel.AllData; and go like
<ListBox x:Name="AllMatching"
ItemsSource="{Binding Path=AllData}"
...
/>
And after all of this - please man check out some tutorials. And also refactor your code from VM_Data to DataViewModel thank you sir.

Using ICustomTypeDescriptor with an ItemsControl

I'm implementing ICustomTypeDescriptor so that I can create types with dynamic properties at runtime, however, instead of using the ICustomTypeDescriptor with a DataGrid which is how most people seem to use it, I'm wanting to use it with an ItemsControl.
Below is the guts of the application. (sorry, it's a lot of code)
public class BindableTypeDescriptor : INotifyPropertyChanged, ICustomTypeDescriptor
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string _propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(_propertyName));
}
}
class BindablePropertyDescriptor : PropertyDescriptor
{
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return m_type; } }
public override Type ComponentType { get { return null; } }
public BindablePropertyDescriptor(BindableTypeDescriptor _owner, Type _type, string _name)
: base(_name, null)
{
m_owner = _owner;
m_type = _type;
m_name = _name;
}
public override void SetValue(object component, object _value)
{
m_owner[m_name] = _value;
}
public override object GetValue(object component)
{
return m_owner[m_name];
}
public override bool CanResetValue(object component)
{
return false;
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
BindableTypeDescriptor m_owner;
Type m_type;
string m_name;
}
public IReadOnlyDictionary<string, object> Properties { get { return m_properties; } }
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return m_properties;
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return new PropertyDescriptorCollection(
m_properties.Select(e => new BindablePropertyDescriptor(this, e.Value != null ? e.Value.GetType() : typeof(object), e.Key))
.ToArray());
}
public object this[string _name]
{
get { return m_properties[_name]; }
set
{
m_properties[_name] = value;
NotifyPropertyChanged(_name);
}
}
Dictionary<string, object> m_properties = new Dictionary<string, object>();
}
public class Element
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string _propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(_propertyName));
}
}
}
public class StringElement : Element
{
string m_value;
public string Value
{
get { return m_value; }
set
{
m_value = value;
NotifyPropertyChanged("Value");
}
}
}
public class NumberElement : Element
{
int m_value;
public int Value
{
get { return m_value; }
set
{
m_value = value;
NotifyPropertyChanged("Value");
}
}
}
public partial class MainWindow : Window
{
public BindableTypeDescriptor CustomType { get; private set; }
public MainWindow()
{
CustomType = new BindableTypeDescriptor();
CustomType["Name"] = new StringElement() { Value = "Dave" };
CustomType["Age"] = new NumberElement() { Value = 5 };
DataContext = this;
InitializeComponent();
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
CustomType["Name"] = new StringElement() { Value = "Fred" };
CustomType["Age"] = new NumberElement() { Value = 6 };
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfApplication1">
<ItemsControl ItemsSource="{Binding CustomType.Properties}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Key}"/>
<ContentPresenter Content="{Binding Value}" Grid.Column="1">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type l:StringElement}">
<TextBox Text="{Binding Value}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:NumberElement}">
<Slider Value="{Binding Value}" Minimum="0" Maximum="50"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The problem is that with the above implementation, the ItemsControl doesn't update when I change a properties value (see OnMouseDoubleClick), which is to be expected as I'm binding to the initial "CustomType.Properties" when I should really be binding to each property by name. I can't do the latter though as I don't know the property names until runtime.
Therefore, I assume I need to be doing the binding dynamically in code behind, but I can't quite figure out how.

Combobox data binding with item templates

I am trying to create a wpf combobox containing a list of World of warcraft items. I am using a item template for the combo box so that the items icon and item name are shown in the combobox drop down list. It works as is and I can start typing a item name and the list of items in the popup are automatically filtered.
The problem is that when I click an item I want the item name to be shown in the comboboxes text area. Instead what I get is a very brief ItemDisplayModel.ToString() then a empty string.
The ItemDisplayModel.ToString() being the ItemDisplayModel type that I selected from the list. Which makes sense cause the combobox control ItemsSource property contains an array of ItemDisplayModel types.
But in my AuctionHouseFilterItemNameModel.Value property I am detecting weather the value has changed and if so rebuild the list of items to be displayed in the combobox. So because the Text in the combobox was changed to ItemDisplayModel.ToString() when I click an item in the popup list it again rebuilds the list of items only this time there is no match because ItemDisplayModel.ToString() does not match any known item names.
All I am trying to do is when I click an item in the popup list I want the ItemDisplayModel.Name to be mapped to the AuctionHouseFilterItemNameModel.Value property and have the combobox text area to display AuctionHouseFilterItemNameModel.Value as well.
Does that make sense? I've tried numerous different ways and I am at a loss. Looking around the web did'nt give me the answer either. I'm sure it's something simple to fix but the solution eludes me.
Here is my XAML for the control
<UserControl
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:AuctionHouseSearchFilters="clr-namespace:Codefarts.WowTracks.DataMinerWPF.Models.AuctionHouseSearchFilters"
xmlns:Models="clr-namespace:Codefarts.WowTracks.DataMinerAppCore.Models;assembly=Codefarts.WowTracks.DataMinerAppCore"
xmlns:DataMinerAppCore="clr-namespace:Codefarts.WowTracks.DataMinerAppCore;assembly=Codefarts.WowTracks.DataMinerAppCore"
xmlns:dataMinerWpf="clr-namespace:Codefarts.WowTracks.DataMinerWPF"
x:Name="userControl"
x:Class="Codefarts.WowTracks.DataMinerWPF.Controls.AuctionHouseSearchFilters.AuctionHouseItemNameControl"
mc:Ignorable="d"
d:DesignHeight="51" d:DesignWidth="283">
<UserControl.Resources>
<dataMinerWpf:UriToBitmapImageConverter x:Key="UriToImageConverter" />
<BitmapImage x:Key='defaultImage' UriSource='/Resources\118.png' />
<DataTemplate x:Key="ItemTemplate" DataType="Models:ItemDisplayModel" >
<StackPanel Orientation="Horizontal" Margin="0 5 0 5">
<Image Width="50" Height="50" Stretch="Fill" Source="{Binding IconUrl, Converter={StaticResource UriToImageConverter}, TargetNullValue={StaticResource defaultImage}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<AuctionHouseSearchFilters:AuctionHouseFilterItemNameModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<ComboBox x:Name="ItemList" MaxDropDownHeight="592" Grid.Row="0" ItemsSource="{Binding ItemDisplayModels}" ItemTemplate="{StaticResource ItemTemplate}" IsEditable="True" IsTextSearchEnabled="True" IsTextSearchCaseSensitive="False" Text="{Binding Value}" >
</ComboBox>
</Grid>
</UserControl>
Here is my code behind
public partial class AuctionHouseItemNameControl : UserControl, IAuctionHouseFilterControl
{
private Application app;
public AuctionHouseItemNameControl()
{
InitializeComponent();
}
public void SetModel(IAuctionHouseSearchFilter model)
{
this.DataContext = model;
}
public void SetApplication(Application app)
{
this.app = app;
}
private void ItemList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//if (model.Handled)
//{
// return;
//}
// model.Handled = true;
if (e.AddedItems != null && e.AddedItems.Count > 0)
{
var model = this.DataContext as Models.AuctionHouseSearchFilters.AuctionHouseFilterItemNameModel;
var item = e.AddedItems[0] as ItemDisplayModel;
// this.ItemList.SelectedValue = item.Name;
model.Value = item.Name;
// this.ItemList.SelectedItem = item.Name;
}
// model.Handled = false;
}
}
and here is my data models
[Export(typeof(IAuctionHouseSearchFilter))]
public class AuctionHouseFilterItemNameModel : IAuctionHouseSearchFilter, INotifyPropertyChanged
{
private string value;
private bool connected;
private Application app;
private ItemDisplayModel[] displayItems;
private CancellationTokenSource backgroundCancel;
private bool buildingDisplayItems;
private readonly object lockObject = new object();
private ItemDisplayModel displayValue;
// private bool Handled { get; set; }
public ItemDisplayModel DisplayValue
{
get
{
return this.displayValue;
}
set
{
if (value == null)
{
return;
}
this.Value = value.Name;
this.displayValue = value;
this.OnPropertyChanged();
}
}
public string Value
{
get
{
return this.value;
}
set
{
//if (this.Handled)
//{
// return;
//}
if (value == this.value)
{
return;
}
this.value = value;
// this.Handled = true;
this.OnPropertyChanged();
var cancellationTokenSource = this.backgroundCancel;
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
}
this.BuildDisplayItems();
// this.Handled = false;
}
}
public IEnumerable<AuctionDataModel> Filter(MainFilterModel model, IEnumerable<AuctionDataModel> items)
{
if (!this.connected)
{
return items;
}
// get item id from it's name
var list = this.app.ItemNames[model.SelectedRegion];
var id = list.FirstOrDefault(x => x.Name.ToLowerInvariant().Contains(this.value.Trim().ToLowerInvariant()));
return items.Where(x => id != null && x.ItemId == id.Id);
}
public IEnumerable<ItemDisplayModel> ItemDisplayModels
{
get
{
return this.displayItems;
}
}
public async void BuildDisplayItems()
{
if (this.buildingDisplayItems)
{
return;
}
if (!this.connected)
{
this.displayItems = null;
}
// return this.GetDisplayItems();
this.buildingDisplayItems = true;
this.backgroundCancel = new CancellationTokenSource();
this.OnPropertyChanged("ItemDisplayModels");
await Task.Factory.StartNew(
() =>
{
var originalItems = this.displayItems;
this.displayItems = new[] { new ItemDisplayModel() { Name = "Rebuilding list..." } };
var correctedSearchValue = this.value.Trim().ToLowerInvariant();
//lock (this.lockObject)
//{
this.displayItems = (string.IsNullOrWhiteSpace(this.value) ?
this.app.DisplayItemModels :
this.app.DisplayItemModels.Where(x => x.Name.ToLowerInvariant().Contains(correctedSearchValue))).Take(100).AsParallel().ToArray();
this.buildingDisplayItems = false;
this.OnPropertyChanged("ItemDisplayModels");
},
this.backgroundCancel.Token);
}
public string Name
{
get
{
return "Item Name";
}
}
public Type Control
{
get
{
return typeof(AuctionHouseItemNameControl);
}
}
public virtual IAuctionHouseSearchFilter Clone()
{
return new AuctionHouseFilterItemNameModel()
{
Value = this.Value
};
}
public void Connect(Application app)
{
if (this.connected)
{
return;
}
this.app = app;
this.connected = true;
}
public void Disconnect()
{
if (!this.connected)
{
return;
}
this.app = null;
this.connected = false;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ItemDisplayModel : INotifyPropertyChanged
{
private string iconUrl;
private string region;
private string name;
private int itemId;
public int ItemId
{
get
{
return this.itemId;
}
set
{
if (value == this.itemId)
{
return;
}
this.itemId = value;
this.OnPropertyChanged();
}
}
public string Name
{
get
{
return this.name;
}
set
{
if (value == this.name)
{
return;
}
this.name = value;
this.OnPropertyChanged();
}
}
public string Region
{
get
{
return this.region;
}
set
{
if (value == this.region)
{
return;
}
this.region = value;
this.OnPropertyChanged();
}
}
public string IconUrl
{
get
{
return this.iconUrl;
}
set
{
if (value == this.iconUrl)
{
return;
}
this.iconUrl = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The problem is that when I click an item I want the item name to be shown in the comboboxes text area. Instead what I get is a very brief ItemDisplayModel.ToString() then a empty string.
To get the Name property displayed in the editable text area of the ComboBox use TextSearch.TextPath so your ComboBox definition would look like:
<ComboBox x:Name="ItemList"
Grid.Row="0"
IsEditable="True"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding ItemDisplayModels}"
MaxDropDownHeight="592"
TextSearch.TextPath="Name" />
Now when an item is selected from the dropdown instead of seeing ItemDisplayModel.ToString(), you will see the Name property.

WPF MVVM User Control binding problem

I have an wpf mvvm application. I try to write checkbox list control.
I can bind the checkbox list elements.
Added to this issue, I want to get sum of the selected checkbox list elements values.
I added DependencyProperty and bind it to view model property.
But, they dont fire each other.
CheckBoxList User Control Xaml
<ListBox x:Name="ItemsControl" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsSelected, Mode=TwoWay}"
Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
CheckBoxList Code Behind
public partial class CheckBoxList : UserControl
{
public CheckBoxList()
{
InitializeComponent();
}
public static readonly DependencyProperty SelectedCheckBoxItemsValueProperty =
DependencyProperty.Register("SelectedCheckBoxItemsValue", typeof(int), typeof(CheckBoxList),
new FrameworkPropertyMetadata(
0,
new FrameworkPropertyMetadata(0, OnSelectedItemsChanged));
public int SelectedCheckBoxItemsValue
{
get { return (int)GetValue(SelectedCheckBoxItemsValueProperty); }
set { SetValue(SelectedCheckBoxItemsValueProperty, value); }
}
private static int GetSelectedCheckBoxItemsValue(DependencyObject obj)
{
return (int)obj.GetValue(SelectedCheckBoxItemsValueProperty);
}
private static void OnSelectedItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CheckBoxList checkboxList = obj as CheckBoxList;
ObservableCollection<ISelectableItem> items = checkboxList.DataContext as ObservableCollection<ISelectableItem>;
foreach (var item in items)
{
item.IsSelected = (GetSelectedCheckBoxItemsValue(obj) & item.Value) != 0;
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBoxList checkboxList = sender as CheckBoxList;
ObservableCollection<ISelectableItem> coll = ItemsControl.DataContext as ObservableCollection<ISelectableItem>;
if (coll == null) return;
int count = 0;
foreach (var item in coll)
{
if (item.IsSelected)
{
count += item.Value;
}
}
SelectedCheckBoxItemsValue = count;
}
}
SelectableItem Class
public interface ISelectableItem : INotifyPropertyChanged
{
bool IsSelected { get; set; }
string Text { get; set; }
int Value { get; set; }
string GroupName { get; set; }
}
public class SelectableItem : ISelectableItem
{ ....
ViewModel Property
public int SelectedCheckBoxEnumItemsValue
{
get
{
return _selectedCheckBoxEnumItemsValue;
}
set
{
_selectedCheckBoxEnumItemsValue = value;
NotifyOfPropertyChange("SelectedCheckBoxEnumItemsValue");
}
}
At Binder Class
string selectedItemPropertyName = "Selected" + viewModelProperty.Name + "Value";
var property = viewModelProperties.FirstOrDefault(p => p.Name.Contains(selectedItemPropertyName));
if (property != null)
{
var selectedItemOrValueBinding = new Binding(property.Name)
{
Mode = property.CanWrite ? BindingMode.TwoWay : BindingMode.OneWay,
ValidatesOnDataErrors = Attribute.GetCustomAttributes(property, typeof(ValidationAttribute), true).Any()
};
BindingOperations.SetBinding(control, CheckBoxList.SelectedCheckBoxItemsValueProperty, selectedItemOrValueBinding);
}
Below code solves your problem..
Please Note the segrgation of view models.
<StackPanel>
<TextBlock Text="{Binding Count}"></TextBlock>
<ListBox x:Name="ItemsControl" ItemsSource="{Binding CheckList}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="item" Content="{Binding Text}" IsChecked="{Binding IsSelected, Mode=TwoWay}" Command="{Binding CheckboxCheckedCommand}" CommandParameter="{Binding IsChecked, ElementName=item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MasterViewModel();
}
}
public class MasterViewModel : INotifyPropertyChanged
{
private List<CheckBoxItem> checkList;
private int count;
public int Count
{
get
{
return count;
}
set
{
count = value;
OnPropertyChanged("Count");
}
}
public List<CheckBoxItem> CheckList
{
get
{
return checkList;
}
set
{
checkList = value;
OnPropertyChanged("CheckList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MasterViewModel()
{
checkList = new List<CheckBoxItem>();
for (int i = 0; i < 5; i++)
{
CheckBoxItem item = new CheckBoxItem();
item.Text = i.ToString();
item.IsSelected = false;
item.CheckboxCheckedCommand = new RelayCommand(new Action<object>(ExecuteCheckCommand));
checkList.Add(item);
}
}
private void ExecuteCheckCommand(object parameter)
{
if (parameter.GetType() == typeof(bool))
{
bool value = bool.Parse(parameter.ToString());
int val = count;
if (value)
{
val++;
}
else
{
val--;
}
Count = val;
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
public class CheckBoxItem : INotifyPropertyChanged
{
private bool isSelected;
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
}
}
public bool IsSelected
{
get
{
return isSelected;
}
set
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public ICommand CheckboxCheckedCommand
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
public class RelayCommand : ICommand
{
private Action<object> executeCommand;
public RelayCommand(Action<object> executeCommand)
{
this.executeCommand = executeCommand;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeCommand(parameter);
}
}

Resources