WPF MultiValueConverter Data Binding to string value not reference - c#

I am new to WPF and data binding so I could have easily missed something in my research or I have been am using the wrong search terms (more likely) to find a solution.
The value of a binding seems to be getting passed and not a reference to the object so when the value gets set in the code behind it does not get updated.
In trying to generalize an OpenFileDialog to be useful on some different tabs of a tab control. I created a custom data object that holds the parameters (Path, Filter, and TextBox)
class OpenFileCommandParameters
{
public string Filter { get; set; }
public string Path { get; set; }
public string TextBox { get; set; }
}
class OpenFileCommandParamtersConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
OpenFileCommandParameters parameters = new OpenFileCommandParameters();
if (values[0] is string) parameters.Filter = (string)values[0];
if (values[1] is string) parameters.Path = (string)values[1];
if (values[2] is string) parameters.TextBox = (string)values[2];
return parameters;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The XAML for passing the information looks like:
<TextBox Name="ButtonTagImportFileName" Text="{Binding Path=TagImportTabVM.TbFileName}" Height="23" HorizontalAlignment="Left" Margin="83,17,0,0" VerticalAlignment="Top" Width="221" />
<Button Name="TagImportOpenFile" Content="Open File" Command="{Binding Path=OpenFileCommand}" Height="23" HorizontalAlignment="Left" Margin="342,17,0,0" VerticalAlignment="Top" Width="98" >
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource openFileCommandParametersConverter}">
<MultiBinding.Bindings>
<Binding Source="XML files (*.xml)|*xml|All files (*.*)|*.*"/>
<Binding Path="AppPath"/>
<Binding Path="TagImportTabVM.TbFileName"/>
</MultiBinding.Bindings>
</MultiBinding>
</Button.CommandParameter>
Both the textbox and the Open File button have bindings to the same string property.
The property gets updated through the execution of the Command
private void OpenFile(object parameter)
{
var parameters = parameter as OpenFileCommandParameters;
FileDialog.Filter = parameters.Filter;
FileDialog.InitialDirectory = parameters.Path;
if (parameters == null) return;
var result = FileDialog.ShowDialog();
if (result == true)
{
parameters.TextBox = FileDialog.SafeFileName;
}
}
Once this command finishes I would expect the value of TbFileName to be the same as what came from the file dialog. This is not the case. as seen from a break point right before the end of the OpenFile block.
I appreciate any assistance you can offer me.

I believe this does not work because the MultiBinding never tries to update its source bindings. It may be possible to force this some how, but when I tried using
BindingOperations.GetBindingExpression(TagImportOpenFile, Button.CommandParameterProperty)
it always returned null, so I'm not sure an approach with a binding on CommandParameters would work. Getting the expression for CommandProperty works fine, CommandParameters must be different... somehow.
I think a way to do this that would definitely work would be:
a) Convert your parameters class into a ViewModel (or at least have it as INotifyPropertyChanged/DependencyObject for two-way binding), the TextBox property would probably need to be renamed to something like 'FileName'.
b) Put an instance of this class on your screens ViewModel ('TagImportTabVM' in this case?),
c) Change your XAML to something like the following:
<TextBox Name="ButtonTagImportFileName" Text="{Binding Path=OpenFileVM.FileName}" Height="23" HorizontalAlignment="Left" Margin="83,17,0,0" VerticalAlignment="Top" Width="221" />
<Button Name="TagImportOpenFile" Content="Open File" Command="{Binding Path=OpenFileCommand}" CommandParameter="{Binding OpenFileVM}"
Height="23" HorizontalAlignment="Left" Margin="342,17,0,0" VerticalAlignment="Top" Width="98" />
This is assuming that you don't mind setting the file filter in code when you make the new instance of the parameters (since I assumed you did something like this for AppPath anyway).

I was pretty much to the solution, and #a little sheep helped direct me the rest of the way with his solution. Just wanted to fully wrap up the question.
I was thinking that because the source was not getting updated that I was breaking the binding when I was setting the value through code. This is not the case. It was just simply not knowing that the value had changed so it did not know to update the source.
Once I discovered this, it was straight forward to find out how to notify the system to let it know it needed to update the source. This gets added to the private void OpenFile(object parameter)
BindingExpression binding = BindingOperations.GetBindingExpression(parameters.PassedTextBox, TextBox.TextProperty);
binding.UpdateSource();
Thats all that was added to get it to update properly.

Related

WPF Repeat password IMultiValueConverter

I want to have a PasswordBox and another PasswordBox to repeat the chosen password and a submit button.
This is what i got:
WPF:
<UserControl.Resources>
<converter:PasswordConverter x:Key="PasswdConv"/>
</UserControl.Resources>
<PasswordBox PasswordChar="*" Name="pb1"/>
<PasswordBox PasswordChar="*" Name="pb2"/>
<Button Content="Submit" Command="{Binding ChangePassword}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource PasswdConv}">
<MultiBinding.Bindings>
<Binding ElementName="pb1"/>
<Binding ElementName="pb2"/>
</MultiBinding.Bindings>
</MultiBinding>
</Button.CommandParameter>
</Button>
Converter:
public class PasswordConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
List<string> s = values.ToList().ConvertAll(p => SHA512(((PasswordBox)p).Password));
if (s[0] == s[1])
return s[0];
return "|NOMATCH|";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Command(ChangePassword):
if ((p as string) != "|NOMATCH|")
{
MessageBox.Show("Password changed");
}
else
{
MessageBox.Show("Passwords not matching");
}
When I set a breakpoint in the converter and in the command I get the following result:
As soon as the view loads it jumps into the converter and tries to convert two PasswordBoxes. Both are empty.
When I press the Button(It doesn't matter what's in the two PasswordBoxes) it doesn't get into the converter and stops at the command-if. P represents an empty password.
The Convert method will only be called when a source property of the multi binding changes, but you are binding to the PasswordBox itself and it never changes.
And binding to the Password property won't work either as the PasswordoBox raises no change notification when this property changes.
It does raise a PasswordChanged event though, so you could handle this and set the CommandParameter property in this event handler instead of using a converter:
private void OnPasswordChanged(object sender, RoutedEventArgs e)
{
string password = "|NOMATCH|";
List<PasswordBox> pb = new List<PasswordBox>(2) { };
List<string> s = new List<string>[2] { SHA512(pb1.Password), SHA512(pb2.Password) };
if (s[0] == s[1])
password = s[0];
btn.CommandParameter = password;
}
XAML:
<PasswordBox PasswordChar="*" Name="pb1" PasswordChanged="OnPasswordChanged"/>
<PasswordBox PasswordChar="*" Name="pb2" PasswordChanged="OnPasswordChanged"/>
<Button x:Name="btn" Content="Submit" Command="{Binding ChangePassword}" />
If you want to be able to reuse this functionality across several views and PasswordBox controls, you should write an attached behaviour.
There are multiple wrong things to consider in your code:
Your binding is directly onto control element (in your case the PasswordBox), you should always bind on a (dep) property of it using the "Path" attribute, if you want that the binding is applied more than once for value observation (Why should the framework otherwise fire an PropertyChanged event? Your control doesn't change, but their properties may change )
If you use TextBox instead of PasswordBox and add Path="Text" to your Bindings, you get the behavior what you expected
Bad news: You can not bind to Password property of a PasswordBox due to security reasons.

How to handle ArgumentException thrown by generated XAML-code in UWP App

i've got several Textboxes in my UWP app which are bound to float properties (two-way). I use compiled bindings. At this time there is no own "code-behind" for these textboxes. Now i got the problem that the app crashes on simple mistypings (for instance if the user types letters instead of numbers). I wonder how i could handle these exceptions without modifying the generated code.
<TextBox Text="{x:Bind ViewModel.QtyGoodEntered, Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="1" FontSize="36" x:Name="QtyGoodTextBox"/>
The App crashes when trying to convert the letters to float.
case 33: // Views\ProdFeedbackView.xaml line 191
this.obj33 = (global::Windows.UI.Xaml.Controls.TextBox)target;
(this.obj33).LostFocus += (global::System.Object sender, global::Windows.UI.Xaml.RoutedEventArgs e) =>
{
if (this.initialized)
{
// Update Two Way binding
this.dataRoot.ViewModel.QtyGoodEntered = (global::System.Double) global::Windows.UI.Xaml.Markup.XamlBindingHelper.ConvertValue(typeof(global::System.Double), this.obj33.Text);
}
Regards
Nils
ok i got following options.
Using converter (thanks to Florian Moser)
TextBoxMask Property (thanks to Marian Dolinsky)
Catching UnhandledException
Due to lack of time i stick with the converter solution but i think the TextBoxMask Property is worth a try. Catching the UnhandledException is the worst solution in my opinion.
This is what my converter looks like. In case of an exception the content defaults to 0.0. Thats all i needed.
public class StringToDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
try
{
return System.Convert.ToString((double)value);
}
catch
{
return "";
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
try
{
return System.Convert.ToDouble((string)value);
}
catch
{
return 0.0;
}
}
}
And thats the XAML part
<TextBox Text="{x:Bind ViewModel.QtyGoodEntered, Mode=TwoWay, Converter={StaticResource StringToDoubleConverter}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="1" FontSize="36" x:Name="QtyGoodTextBox"/>

How to modify Image Source in WPF XAML dynamically

I have a WPF App that has (so far) 2 modes of display, regularmode and widgetmode.
I am using Prism 6 with MVVM design pattern.
MainWindowViewModel knows the mode of display.
ToolBarView has, as expected, a toolbar of buttons and the buttons shall be dynamically changed to different images depending on the mode of the view. If the mode is WidgetMode, it switches to the image with an identical name but with an '_w' added. So instead of "image.png", it's "image_w.png".
What I'd like to do is create a string in ToolBarView that is updated to either String.Empty or to "_w", depending on the mode. I'd also like the image root folder to be a global string, rather than a hardcoded string, so I have defined that in app.xaml.
<Application.Resources>
<sys:String x:Key="ImageURIRoot">/MyApp;component/media/images/</sys:String>
</Application.Resources>
Then in my toolbarview (a usercontrol), I did this:
<UserControl.Resources>
<converters:StringToSourceConverter x:Key="strToSrcConvert"/>
<sys:String x:Key="BtnImgSuffix">_w</sys:String>
.
.
.
</UserControl.Resources>
Note that the string is hardcoded; eventually, I will change it dynamically based off the windowmode.
I then put the Buttons in a Listbox
<ListBoxItem Style="{StaticResource MainButton_Container}">
<Button Command="{Binding ButtonActionDelegateCommand}" Style="{StaticResource Main_Button}">
<Image Source="{Binding Source={StaticResource ImageURIRoot}, Converter={StaticResource strToSrcConvert}, ConverterParameter='{}{0}button.png'}" />
</Button>
</ListBoxItem>
Converter code:
public class StringToSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is string)
{
return string.Format(parameter.ToString(), value);
}
return null;
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
So that works. But what I want is to have the ConverterParameter equal "{}{0}button{1}.png", where {0} is the URI Root and {1} is the suffix. But I can't figure out how to do it. I know it's simple, but I can't put my finger on it!
Please help!
Figured it out and it was through multibinding. The way I did it was create a converter that inherits from IMultiValueConverter. Its "Convert" method looks like this:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ImageSourceConverter conv = new ImageSourceConverter();
int suffixPos = ((String)parameter).Length - 4;
var returnValue = ((String)parameter).Insert(suffixPos, values[1].ToString());
returnValue = Path.Combine(values[0].ToString(), returnValue);
ImageSource imgsrc = conv.ConvertFromString(returnValue) as ImageSource;
return imgsrc;
}
The xaml looks like this:
<Image Height="30" Width="40" diag:PresentationTraceSources.TraceLevel="High">
<Image.Source>
<MultiBinding Converter="{StaticResource stringsToSrcConvert}" ConverterParameter="buttonImg.png">
<Binding Source="{StaticResource ImageURIRoot}"/>
<Binding Source="{StaticResource BtnImgSuffix}"/>
</MultiBinding>
</Image.Source>
</Image>
Also, had to modify the URIRoot
<Application.Resources>
<sys:String x:Key="ImageURIRoot">pack://application:,,,/MyApp;component/media/images/</sys:String>
</Application.Resources>
Thanks, Clemens!

Dynamic custom content in RichTextBox

I wish to display a text + hyperlinks in a RichTextBox from the code-behind or the binded via the Xaml if there is the possibility.
For the moment, I have a string variable with a Url (that I'd like very much to make clickable) binded to a TextBlock. I'd like to basically replace:
<TextBlock Text="{Binding myTextWithUrl}" />
by (in a richTB: )
<Run Text="partOfTextNonUrl" /><Hyperlink NavigateUri="theUrl" TargetName="whatever" />
Here is how it is presented:
I have an ItemsControl templated with a custom object
<ItemsControl ItemsSource="{Binding FeedResults}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" >
<my:SearchResultItem />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And this custom control presents the binded data in 3 TextBlocks as presented above: title, date, and the text containing text + urls.
I have already a method that extracts the urls from the string, I just don't know how to use it. I can generate dynamically Run() and Hyperlink(), and add them to the paragraph, but how to bind ?
Or any other solution ? You'd make my day!!
Thanks, Sylvain
OK. So apparently inline Hyperlinks aren't even allowed in Silverlight. But you can make your own!
http://csharperimage.jeremylikness.com/2009/11/inline-hyperlinks-in-silverlight-3.html
Not easy - at least not as easy at it should be. But it should get the job done.
Once you have the ability to add these runs with hyperlinks, the way I'd approach it is this. Create a user control with a single TextBlock (txtContent). Set the DataContext="{Binding myTextWithUrl}". Then in the code behind:
public TextWithUrlUserControl()
{
InitializeComponent();
this.Loaded += (s, e) =>
{
foreach(var inline in ParseText(DataContext as string))
txtContent.Inlines.Add(inline);
};
}
IEnumerable<Inline> ParseText(string text)
{
// return list of Runs and Runs with hyperlinks using your URL parsing
// for demo purposes, just hardcoding it here:
return new List<Inline>
{
new Run{Text="This text has a "},
new Run{Text="URL", RunExtender.NavigateUrl="http://www.google.com/"},
new Run{Text="in it!"}
};
}
Hope this is helpful.
I would do something like this. Create a ValueConverter which will take your text (with the URL in it). Then in your TextBlock, create the Run and Hyperlink - bind both to the text, both using the ValueConverter, but with a different parameter to the ValueConverter.
The ValueConverter:
public class MyCustomValueConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(parameter.ToString()== "URL")
{
// return the URL part of the string
}
else
{
// return the non-URL portion of the string
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then your XAML looks like this:
<Run Text="{Binding myTextWithUrl, Converter={StaticResource valueConverter}}"></Run><Hyperlink NavigateUri="{Binding myTextWithUrl, Converter={StaticResource valueConverter}, ConverterParameter=URL}"></Hyperlink>

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

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...

Categories

Resources