Adding IValueConverter breaks updates when changed occur from observable collection to UI - c#

I have a itemscontrol with the item template set to a border, then i bind the datacontext of the listview to a list of objects that contain a bool property.
Then i added a click event handler to the border, and when detecting a click, i cast the datacontext of the border to the class and set the bool field to true.
That works as a charm, but i want the rectangle to have a specific colour when the bool field is set to true or false, so i created a IValueConverter that takes my class and returns a colour.
That works too, the rectangles are different colors based on the bool field.
I am still able to click the rectangles, but they just arent updated.
The color of the rectangle wont change.
Datatemplate from the itemscontrol itemtemplate
<DataTemplate>
<Border ToolTip="{Binding Seat.Column}" Width="25" Height="25" Margin="0,0,2,2" BorderBrush="Black" Background="{Binding Converter={StaticResource ResourceKey=SeatStateConverter}}" BorderThickness="2" Name="rectangle1" VerticalAlignment="Center" HorizontalAlignment="Center" MouseLeftButtonDown="rectangle1_MouseLeftButtonDown">
<Label Content="{Binding Occupied}" Foreground="White" FontSize="7"></Label>
</Border>
</DataTemplate>
The click event
private void rectangle1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Border item = sender as Border;
SeatState state = item.DataContext as SeatState;
state.Locked = !state.Locked;
}
my converter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SeatState state = value as SeatState;
if (state == null)
return null;
SolidColorBrush brush = new SolidColorBrush();
if (state.Occupied)
{
brush.Color = Color.FromRgb(172, 0,0);
}
else if (state.Locked)
{
brush.Color = Color.FromRgb(214, 65, 0);
}
else if(!state.Occupied)
{
brush.Color = Color.FromRgb(0, 172, 0);
}
return brush;
}
This works great.. untill i add the converter that converts the objects into a SolidColorBrush.
I tried all sorts of crazy stuff that should have nothing to do with my problem.
implementing the convertback method
in the IValueConverter interface
setting the binding to a two way binding
invoking the UpdateLayout
method on the ItemsControl
But nothing seemed to work.
Anyone got any ideas?
My english could be better so please ask if there is anything you want clearified =)
Thanks in advance.

I think you are binding to the SeatState object - whereas you actually need to bind to some combination of the Occupied an Locked properties?
i.e. it's not the SeatState object itself that is changing, but rather its a couple of the properties of the SeatState.
Maybe merge the properties together somehow and set this merged property as the Path for the XAML Background.
e.g. within SeatState
private bool _Locked
public bool Locked
{
get
{
return _Locked;
}
set
{
_Locked = value;
NotifyPropertyChange("Locked");
NotifyPropertyChange("LockedAndOccupied");
}
}
private bool _Occupied
public bool Occupied
{
get
{
return _Occupied;
}
set
{
_Occupied = value;
NotifyPropertyChange("Occupied");
NotifyPropertyChange("LockedAndOccupied");
}
}
public Tuple<bool, bool> LockedAndOccupied
{
get
{
return new Tuple<bool, bool>(Locked, Occupied);
}
}
then in the XAML you can bind to Path=LockedAndOccupied, Converter=...
Obviously you'll have to change the Converter code too - I'll let you do that!
Alternatively... now I've read up about it...
There is something called a MultiBinding - http://www.switchonthecode.com/tutorials/wpf-tutorial-using-multibindings - looks perfect for your needs
Something like:
<Border.Background>
<MultiBinding Converter="{StaticResource aNewConverter}">
<Binding Path="Locked"/>
<Binding Path="Occupied"/>
</MultiBinding>
</Border.Background>
I've learnt something new tonight :)

Check the Background binding... it looks like your Path is missing. I would expect to see something like...
Background="{Binding Path=., Converter={StaticResource ResourceKey=SeatStateConverter}}"
Alternately you could try setting BindsDirectlyToSource=true.
On second thought, you probably need to implement an IMultiValueConverter, and then bind each of the properties separately. That may be what you need to do to get the change notifications on each of the properties. Here is an example of an IMultiValueConverter implementation from MSDN.
Also, you may want to check your implementation of INotifyPropertyChanged... misspellings of property names will break the change notifications...

Related

Two-way-binding: editing passed value from XAML control in the model setter does not update control

This is for a Windows 10 Universal App.
XAML:
<RelativePanel Padding="4" Margin="4,12,0,0">
<TextBlock x:Name="Label" Text="Class Name" Margin="12,0,0,4"/>
<ListView x:Name="ClassTextBoxes"
ItemsSource="{Binding TextBoxList}"
SelectionMode="None" RelativePanel.Below="Label">
<ListView.ItemTemplate>
<DataTemplate >
<RelativePanel>
<TextBox x:Name="tbox"
PlaceholderText="{Binding PlaceHolder}"
Text="{Binding BoxText,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Padding="4" Width="200" MaxLength="25"/>
<TextBlock x:Name="errorLabel"
RelativePanel.Below="tbox"
Text="{Binding Error, Mode=TwoWay}"
Padding="0,0,0,4"
FontSize="10"
Foreground="Red"/>
<Button Content="Delete" Margin="12,0,0,0" RelativePanel.RightOf="tbox"/>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RelativePanel>
Model:
public class TextBoxStrings : BaseModel
{
private string _placeholder;
public string PlaceHolder
{
get { return _placeholder; }
set
{
if (_placeholder != value)
{
_placeholder = value;
NotifyPropertyChanged();
}
}
}
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
}
}
public string CheckBoxText(string val)
{
var r = new Regex("[^a-zA-Z0-9]+");
return r.Replace(val, "");
}
}
ViewModel:
private TrulyObservableCollection<TextBoxStrings> _textBoxList;
public TrulyObservableCollection<TextBoxStrings> TextBoxList
{
get { return _textBoxList; }
set
{
if (_textBoxList != value)
{
_textBoxList = value;
RaisePropertyChanged();
}
}
}
and I add new TextBoxString objects to my TextBoxList collection from within my view-model.
I want to make it that users can't type in certain characters (or rather, they get deleted whenever they
are typed in.
This works...in the model. Setting breakpoints and looking at the values, everything in the Model is working: value goes into the setter and gets changed, _boxText holds the new value that is set from CheckBoxText();
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
So if I type in "abc*()" into "tbox", the value in the model will be "abc". The value of the textbox, however, will still be "abc*()".
I have a feeling it has something to do with the fact that I'm editing items that are inside of a collection and I don't have anything implemented to handle changing items within a collection. I was under the impression that using INotifyPropertyChanged and ObservableCollection<T> would take care of that for me.
Does anyone have any suggestions?
Thank you!
Edit: So, now I'm trying to use TrulyObservableCollection because I thought this was the problem, but it hasn't helped. Here it is: https://gist.github.com/itajaja/7507120
But the problem is, in my View, the textbox doesn't reflect changes to the underlying text that I make in the model.
As you've seen, the TextBox do reflect changes to your model. When you type in "abc*()" in the TextBox, the value in the model will be changed to "abc". The problem here is that the binding system in UWP is "intelligent". For TwoWay bindings, changes to the target will automatically propagate to the source and in this scenario, binding system assumes that the PropertyChanged event will fire for corresponding property in source and it ignores these events. So even you have RaisePropertyChanged or NotifyPropertyChanged in you source, the TextBox still won't update.
In WPF, we can call BindingExpression.UpdateTarget Method to force the update. But this method is not available in UWP.
As a workaround, you should be able to use TextBox.TextChanged event to check the input like following:
private void tbox_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = sender as TextBox;
if (tb != null)
{
var originalText = tb.Text;
var r = new Regex("[^a-zA-Z0-9]+");
if (originalText != r.Replace(originalText, ""))
{
var index = (tb.SelectionStart - 1) < 0 ? 0 : (tb.SelectionStart - 1);
tb.Text = r.Replace(originalText, "");
tb.SelectionStart = index;
}
}
}
However it may break your MVVM model, you can use data validation to avoid this and here is a blog: Let’s Code! Handling validation in your Windows Store app (WinRT-XAML) you can refer to. And for my personal opinion, data validation is a better direction for this scenario.
if (_boxText != value)
{
_boxText = CheckBoxText(value);
NotifyPropertyChanged();
}
Try changing this to:
var tmp = CheckBoxText(value);
if (_boxText != tmp)
{
_boxText = tmp;
NotifyPropertyChanged();
}
I hope, in your XAML, the binding to property BoxText is two-way, right?
You should edit BoxText and then send checked value to UI. Just send value to CheckBoxText and already edited should be assigned to _boxText. And then you should send BoxText to UI by calling RaisePropertyChanged("BoxTest"). Please, see the following code snippet:
private string _boxText;
public string BoxText
{
get { return _boxText; }
set
{
if (_boxText != value)
{
_boxText=CheckBoxText(value);
RaisePropertyChanged("BoxText");
}
}
}
There is no difference where you use INotifyPropertyChanged for one property of for properties placed in collection. The complete example with collections and ListView can be seen here

change textblock text that is inside Listbox in windowsphone 8

i want to change textblock text in page initialize event
here is my xaml
<ListBox Margin="3,60,1,10" BorderThickness="2" Grid.Row="1" Name="lstAnnouncement" Tap="lstAnnouncement_Tap" Width="476" d:LayoutOverrides="VerticalMargin">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="thispanel" Grid.Row="1" Orientation="Horizontal" Height="120" Width="478" >
<StackPanel.Background>
<ImageBrush ImageSource="Images/Text-ALU.png" Stretch="Fill" />
</StackPanel.Background>
<Grid HorizontalAlignment="Left" Width="30" Margin="0,0,0,2" Background="#FF0195D5" Height="118">
<TextBlock x:Name="txtDate" TextWrapping="Wrap">
</TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
i want to change txtDate.Text using c# in code-behind but txtdate is not accessible in code behind so how to achieve it ?
The reason you're not able to access the txtDate object is because it's contained within the DataTemplate you're using for the ListBox. This isn't an error - the DataTemplate is being applied to every single item added to your ListBox.
Given that the ListBox creates, among other controls, a Grid containing a TextBlock with the name "txtDate", for every single item added to it, what would it mean to access the txtDate object? How would your program decide which of a (functionally) infinite number of txtDates associated with an identical number of ListBoxItems you meant when you referenced txtDate?
If you wanted to be able to easily change the content of txtDate, you'd want to bind the ItemsSource of your ListBox to a property in a ViewModel. The easiest way to do this would be to have that property be an IEnumerable containing a custom model type. This way, you could update the text property of that model and call NotifyPropertyChanged on the that property, and the UI would update to reflect the new data.
Here's an example:
public class YourViewModel
{
public List<YourModel> Models { get; set; }
}
public class YourModel : INotifyPropertyChanged
{
private string yourText;
public string YourText
{
get { return yourText; }
set
{
yourText = value;
NotifyPropertyChanged("YourText");
}
}
// add INotifyPropertyChanged implementation here
}
And then you'd want to bind the ItemsSource of the ListBox to YourViewModel's Models property, and the text of your TextBox to the YourModel's YourText property. Any time you change the YourModel.YourText property, it'll automatically update on the UI. I think it's probably subject to debate whether having your model implement INotifyPropertyChanged is proper MVVM, but I find it a lot easier in these cases than forcing the ViewModel to update every single model each time a change is made on one of them.
If you're not familiar with the MVVM pattern used with WPF, this might be a good start: MVVM example.
this function will help you... This will help u find the control inside of a listbox runtime..
public FrameworkElement SearchVisualTree(DependencyObject targetElement, string elementName)
{
FrameworkElement res = null;
var count = VisualTreeHelper.GetChildrenCount(targetElement);
if (count == 0)
return res;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(targetElement, i);
if ((child as FrameworkElement).Name == elementName)
{
res = child as FrameworkElement;
return res;
}
else
{
res = SearchVisualTree(child, elementName);
if (res != null)
return res;
}
}
return res;
}
Here first parameter is parent and the second parameter is the name of the element which in your case is "txtDate".. hope it works!!

Windows Phone update/refresh binding

I have a textblock in my listbox called "feedTitle" which I want to change the forground color of. I use Foreground="{Binding Converter={StaticResource NewsTextColorConverter}}" for the binding of the forground color. Now the strange problem is that, if I choose a color in the listpicker("Lys" or "Dark" value) it runs the IValueConverter Convert method, but it dont show the color in the GUI, only if I restart my whole app it shows the color I chosen. It's like it only set the color of the forground of the textblock once.
MainPage.xaml
<ListBox Grid.Row="1" Name="feedListBox" ScrollViewer.VerticalScrollBarVisibility="Auto" SelectionChanged="feedListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Top">
<TextBlock TextDecorations="Underline" FontSize="24" Name="feedTitle" TextWrapping="Wrap" Margin="12,0,0,0" Foreground="{Binding Converter={StaticResource NewsTextColorConverter}}" Text="{Binding Title.Text, Converter={StaticResource RssTextTrimmer}}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in my app file:
App.xaml
<Application.Resources>
<converter:NewsTextColorConverter xmlns:converter="clr-namespace:NordjyskeRss" x:Key="NewsTextColorConverter" />
</Application.Resources>
I use a listpicker where a user select the value "Mørk" or "Lys" and then I want the textblock forground color to update its forground color. I call the Convert method and pass null as arguments, it seems to run the method fine:
MainPage.cs
private void lpkThemes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Make sure we don't handle the event during initiation.
if (e.RemovedItems != null && e.RemovedItems.Count > 0)
{
if (this.lpkThemes.SelectedItem != null)
{
settings[THEMES_SETTING_KEY] = lpkThemes.SelectedItem.ToString();
if (lpkThemes.SelectedItem.ToString() == "Mørk")
{
n.Convert(null, null, null, null);
}
else
{
n.Convert(null, null, null, null);
}
}
}
}
This is where I use a IValueConverter to check for what color to use on the textblock and then add it:
MainPage.cs
public class NewsTextColorConverter : IValueConverter
{
protected IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
protected const string THEMES_SETTING_KEY = "Themes";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (settings.Contains(THEMES_SETTING_KEY))
{
string themesValue = (string)settings[THEMES_SETTING_KEY];
if (themesValue == "Mørk")
{
return new SolidColorBrush(Colors.Green);
}
else
{
return new SolidColorBrush(Colors.Blue);
}
}
return new SolidColorBrush(Colors.Green);
//throw new NotSupportedException("ColorToBurshConverter only supports converting from Color and String");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I think you need to redesign your app in the following way:
Add the following line into your app.xaml or page resources: <SolidColorBrush x:Key="brushListItemsForeground" Color="#FFFFFFFF" />
Replace Foreground="{Binding Converter={StaticResource NewsTextColorConverter}}" with Foreground="{StaticResource brushListItemsForeground}"
In your SelectionChanged:
var brush = (SolidColorBrush)Application.Current.Resources["brushListItemsForeground"]; if you’ve added the brush to app.xaml, or = (SolidColorBrush)this.Resources["brushListItemsForeground"]; if you’ve added the brush to page resources. Then change the Color property of the brush based on your settings.
P.S. There’re also other correct ways: e.g. create a SettingsContainer class that implements INotifyPropertyChanged, add it into some resource dictionary <local:SettingsContainer x:Key="mySettings" />, then bind to its properties e.g. Foreground="{Binding listItemsForeground, Source={StaticResource mySettings}}", when you need to change the value, change the listItemsForeground property of your class and raise PropertyChanged.
Currently, you’re abusing value converter using then as value providers, they were not designed for that, and that is why you have issues updating those values.

Silverlight TargetNullValue binding for Brush dependency property

I have a reusable user control with a dependency property which sets the color of a rectangle. The property uses Brush as type.
The data binding works fine, however I would like to add a fallback and null value when the binding has errors or its value is not specified.
Here is my XAML:
<Rectangle Fill="{Binding Path=UnderLineColor, ElementName=Header,
FallbackValue=LightGrey, TargetNullValue=LightGrey}"
Height="2"
Margin="0,2"
Grid.Row="1"
Grid.ColumnSpan="2" />
And the code of the UnderLineColor DP:
public Brush UnderLineColor
{
get { return (Brush)GetValue(UnderLineColorProperty); }
set { SetValue(UnderLineColorProperty, value); }
}
public static readonly DependencyProperty UnderLineColorProperty =
DependencyProperty.Register("UnderLineColor", typeof(Brush), typeof(SectionHeader), null);
The issue is that SL doesn't seem to accept the fallback and nullvalue I specify.
What value should I write into these properties to make it work? Or should I use a ValueConverter instead of this approach?
Edit:
Top tip for today: Grey != Gray. Issue is fixed now. :)

WPF Binding to change fill color of ellipse

How do I programmatically change the color of an ellipse that is defined in XAML based on a variable?
Everything I've read on binding is based on collections and lists -can't I set it simply (and literally) based on the value of a string variable? string color = "red" color = "#FF0000"
It's worth pointing out that the converter the other posts reference already exists, which is why you can do <Ellipse Fill="red"> in xaml in the first place. The converter is System.Windows.Media.BrushConverter:
BrushConverter bc = new BrushConverter();
Brush brush = (Brush) bc.ConvertFrom("Red");
The more efficient way is to use the full syntax:
myEllipse.Fill = new SolidColorBrush(Colors.Red);
EDIT in response to -1 and comments:
The code above works perfectly fine in code, which is what the original question was asking about. You also don't want an IValueConverter - these are typically used for binding scenarios. A TypeConverter is the right solution here (because you're one-way converting a string to a brush). See this article for details.
Further edit (having reread Aviad's comment): you don't need to explicitly use the TypeConverter in Xaml - it's used for you. If I write this in Xaml:
<Ellipse Fill="red">
... then the runtime automagically uses a BrushConverter to turn the string literal into a brush. That Xaml is essentially converted into the equivalent longhand:
<Ellipse>
<Ellipse.Fill>
<SolidColorBrush Color="#FFFF0000" />
</Ellipse.Fill>
</Ellipse>
So you're right - you can't use it in Xaml - but you don't need to.
Even if you had a string value that you wanted to bind in as the fill, you don't need to specify the converter manually. This test from Kaxaml:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<Page.Resources>
<s:String x:Key="col">Red</s:String>
</Page.Resources>
<StackPanel>
<Ellipse Width="20" Height="20" Fill="{Binding Source={StaticResource col}}" />
</StackPanel>
</Page>
Strangely, you can't just use the StaticResource col and still have this work - but with the binding it and automatically uses the ValueConverter to turn the string into a brush.
what you will need to do is implement a custom converter to convert the colour to the brush object. Something like this...
public class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
System.Drawing.Color col = (System.Drawing.Color)value;
Color c = Color.FromArgb(col.A, col.R, col.G, col.B);
return new System.Windows.Media.SolidColorBrush(c);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
SolidColorBrush c = (SolidColorBrush)value;
System.Drawing.Color col = System.Drawing.Color.FromArgb(c.Color.A, c.Color.R, c.Color.G, c.Color.B);
return col;
}
}
And then specify that converter in your binding
Fill="{Binding Colors.Red, Converter={StaticResource ColorToBrushConverter }"
use
System.Windows.Media
If the name of your ellipse in your XAML is my_ellipse,
write something like this:
my_ellipse.Fill = System.Windows.Media.Brushes.Red;
or this:
my_ellipse.Fill = (SolidColorBrush)new BrushConverter().ConvertFromString("#F4F4F5")
A quick work around for this (Although its not binding, and less efficient) is to check the status of an object/element and update the new/other object(s) based on the status, I'll provide a Basic Example Below.
You can do this in MVVM by getting the status of MainWindow Objects from a User Control and changing whatever you want from the MainWindow object status.
Note: this will not work for 2 themed applications and other scenarios where more than basic use case is needed. (Example: Dark Mode, Etc)
In my example, I am using data to draw a "text" on screen (FrontFog_Text, Which is why the text is using the .Fill Property in my case).
I am not using this in my application but came across this when testing a few things so I thought I would share in this thread while I search for my answer!
private void FrontFog_ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
if (((MainWindow)App.Current.MainWindow).Theme_Control.IsChecked == true)
{
FrontFog_Text.Fill = new SolidColorBrush(System.Windows.Media.Colors.White);
}
else if (((MainWindow)App.Current.MainWindow).Theme_Control.IsChecked == false)
{
FrontFog_Text.Fill = new SolidColorBrush(System.Windows.Media.Colors.Black);
}
}

Categories

Resources