How to use StringFormat in MultiBinding? - c#

I have the following code:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}{1}">
<Binding Path=".[ServerName]" TargetNullValue=""/>
<Binding Path=".[InstanceName]" TargetNullValue="" StringFormat="{}\\{0}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
but the result I get is "ServerNameInstanceName" instead of "ServerName\InstanceName"

StringFormat is ignored if the target of the binding is not typed as String. Therefore, the StringFormat on the MultiBinding will be used, because TextBlock.Text is String (it wouldn't work if you were binding to Label.Content, because Label.Content is Object -- a common complaint). The inner one, the binding to .[InstanceName], doesn't have a target that's typed as String, because MultiBindings must accept values of any type. So that StringFormat is ignored.
The following works.
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}\{1}">
<Binding Path=".[ServerName]" TargetNullValue=""/>
<Binding Path=".[InstanceName]" TargetNullValue="" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
If you did it your way in order to eliminate the backslash in cases where .[InstanceName] is null, you've got a problem. I would write a multivalue converter to insert the backslash, or not, and skip StringFormat. There's no way to get StringFormat to do what you need it to do here. It's just not smart enough.
C#
public class ServerInstance : MarkupExtension, IMultiValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
var server = $"{values[0]}0";
var instance = $"{values[1]}";
if (instance == "")
return server;
if (server == "")
return "";
return $"{server}\\{instance}";
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{local:ServerInstance}">
<Binding Path=".[ServerName]" />
<Binding Path=".[InstanceName]" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>

Related

How to keep my items in datagrid in sequence order when item from middle is deleted WPF C#

I am loading items from database to my DataGrid on this way (on form load):
dataGridArticles.ItemsSource = null;
dataGridArticles.ItemsSource = listCurrentArticles;
And I got ability to add item to my datagrid list, and also I have ability to remove them by pressing DEL key <datagrid automatically removes it from a list>.
But When I am entering item to my list which is source to datagrid I am also setting OrdinalNumber for each item, for example when I adding new item to a list I am doing next thing:
article.OrdinalNumber = GetCurrentlyMaxOrdinalNumber()+1;
so if I'm adding items one after other, ordinals numbers will be 1,2,3,4,5... and so on, something like this (marked red) :
My stored procedure which is returning max ordinal number from articles table:
CREATE DEFINER=`root`#`localhost` PROCEDURE `ArticleGetNextCodeNumber`()
BEGIN
Select ifnull(max(OriginalArticleCode),0)
from articles;
END
And this is what is happening when I am adding new item to my list:
I am setting next ordinal numbers and some other attributes for new article:
private void btnSaveChangesOnArticle_Click(object sender, RoutedEventArgs e)
{
try
{
if (newItem)
{
Article newArticle = new Article();
newArticle.ArticleCode = txtArticleCode.Text.Trim();
newArticle.OriginalArticleCode = Convert.ToInt32(ArticlesController.Instance.GetNextArticleCode()) + 1; // Here I'm setting new article ordinal number
newArticle.Title = txtArticleTitle.Text.Trim();
newArticle.Price = Convert.ToDecimal(txtPrice.Text.Trim());
var lastInserted = ArticlesController.Instance.Save(newArticle);
MessageBox.Show("Operation Successful.");
listCurrentArticles.Add(lastInserted);
dataGridArticles.ItemsSource = null;
dataGridArticles.ItemsSource = listCurrentArticles;
}
}
}
}
And I am wondering right now, when user press DELETE KEY to remove some item from my datagrid,how can I reorganize my ordinal numbers, because if I add 5 items for example to my datagrid, and if user delete second item, than my order might look like this: 1 3 4 5.. insted of 1 2 3 4
So my question is guys, how can i reorganize that, when something changes to keep it sequenced all the time ( so maybe then I dont need to keep ordinal number in database, because it is not important to me, I am using it just because of better UI look, to show to user how many items he added to that DataGrid).
Thanks a lot,
Cheers!
You could replace the OrdinalNumber DataGridTextColumn with a DataGridTemplateColumn and use a converter to return the value of the GetIndex() method of the parent DataGridRow container.
You need to bind to both the parent DataGridRow and the Count property of the Items property of the DataGrid itself for the binding to get refreshed when the number of items changes, i.e. when an item is added or removed.
This should be pretty simple to implement. You create a class that implements the IMultiValueConverter interface:
namespace WpfApplication3
{
public class RowNumberConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return ((values[0] as DataGridRow).GetIndex() + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
And use it like this:
<DataGrid x:Name="dataGridArticles" AutoGenerateColumns="False" xmlns:local="clr-namespace:WpfApplication3">
<DataGrid.Resources>
<local:RowNumberConverter x:Key="RowNumberConverter" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="#" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource RowNumberConverter}">
<Binding Path="." RelativeSource="{RelativeSource AncestorType=DataGridRow}" />
<Binding Path="Items.Count" RelativeSource="{RelativeSource AncestorType=DataGrid}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- + the rest of your columns -->
</DataGrid.Columns>
</DataGrid>
Check out this related question that might solve your problem.
Column/Row index in a DataGrid column
Your best option is to remove OrdinalNumber property (Since you do not need it, as you said) and use DataGrid.RowHeaderTemplate with a converter. Here is a working example:
MainWindow
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
listCurrentArticles = new ObservableCollection<Article>() { new Article { Text = "k" }, new Article { Text = "l" } };
}
public ObservableCollection<Article> listCurrentArticles { get; set; }
Xaml
<Window.Resources>
<local:HeaderConverter x:Key="headerConverter"/>
</Window.Resources>
<Grid>
<DataGrid DataContext="{Binding}" ItemsSource="{Binding listCurrentArticles}">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock MinWidth="25" TextAlignment="Center">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource headerConverter}">
<Binding Path="ItemsSource" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGrid}" />
<Binding Path="Item" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
</DataGrid>
</Grid>
Converter
public class HeaderConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<Article> list = values[0] as ObservableCollection<Article>;
Article obj = values[1] as Article;
int ind = list.IndexOf(obj);
if (ind == -1)
return "+";
else
return (ind + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Using a MultiConverter with ComboBox.SelectedValue: value for "ConvertBack" is always null

I have the following scenario: In my View Model I have two properties, let them be CreateNewThing and SelectedExistingThing. CreateNewThing is of type bool and SelectedExistingThing is of type Thing.
I now have a ComboBox that displays two static entries, "None" and "Create new", and a list of Things. I wired it up like this:
Collection for the list of things:
<ComboBox.Resources>
<CollectionViewSource x:Key="AllThings" Source="{Binding ViewModel.AllThings, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyCustomControl}}}" />
<tools:ThingSelector x:Key="ThingSelector" />
</ComboBox.Resources>
Setup for the ItemsSource:
<ComboBox.ItemsSource>
<CompositeCollection>
<tools:PlaceholderForNone />
<tools:PlaceholderForNew />
<Separator />
<CollectionContainer Collection="{Binding Mode=OneWay, Source={StaticResource AllThings}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
Binding for SelectedValue:
<ComboBox.SelectedValue>
<MultiBinding Converter="{StaticResource ThingSelector}">
<Binding Path="ViewModel.CreateNewThing" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:MyCustomControl}}" />
<Binding Path="ViewModel.SelectedExistingThing" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type local:MyCustomControl}}" />
</MultiBinding>
</ComboBox.SelectedValue>
My ThingSelector looks like this:
internal sealed class ThingSelector: IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if((values == null) ||
(values.Length != 2) ||
(!(values[0] is bool))) {
return Binding.DoNothing;
}
if((bool)values[0]) {
return new PlaceholderForNew();
}
if(values[1] is Thing) {
return values[1];
}
return new PlaceholderForNone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
if(value is PlaceholderForNew) {
return new object[] { true, null };
}
return new object[] { false, value as Thing};
}
}
The PlaceholderForXXX are done in a way that their HashCode is always 0 and Equals is true for all objects of the same type, so all two PlaceholderForXXX are equal.
The strange thing is now: I can select the "None" and "Create new" options from my combo box, and they are correctly propagated (i.e., selecting the "new" option sets the CreateNewThing property to true and the SelectedExistingThing property to null).
However, when I select a Thing from the combo box, the ConvertBack method is invoked with the parameter value being null where, to my understanding, it should be the selected value of the ComboBox which is clearly not null (if I put an appropriate event into the code-behind, I really see that SelectedValue is a Thing and not null).
What am I missing?

Using MultiBinding to calculate sum of multiple textboxes

Based on this previously answered question, I'm trying to create an IMultiValueConverter that will allow the Text property of a TextBox in WPF to be bound to the sum of several other TextBox values. I've mirrored the answer to the referenced question fairly strictly, yet when testing this I get an InvalidCastException. In the code below, the lines commented out are the code from the aforementioned answer. I did try running this with the var datatype instead of using double (I dislike var, just a preference), and received the
same error in the same place. I've tried changing the style of cast in various ways, including Convert.ToInt32, (int), and even int.Parse, but everything results in the same error, same location.
Does anybody have a clue as to what the problem with this could be? This is my first real foray into binding like this, so it could be I'm fundamentally misunderstanding it, but honestly don't think that's what it is...
public class AddListRecordsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 0.0;
try
{
double[] doubleValues = values.Cast<double>().ToArray();
foreach (var doubleValue in doubleValues)
{
result += doubleValue;
}
//var doubleValues = values.Cast<double>().ToArray();
//var leftPart = string.Join(" x ", doubleValues);
//var rightPart = doubleValues.Sum().ToString();
//var result = string.Format("{0} = {1}", leftPart, rightPart);
//return result;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Destination TextBox:
<TextBox x:Name="allRecords" Style="{StaticResource dataEntryTextBox}" Grid.Column="1" Grid.Row="6">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AddListRecordsConverter}">
<Binding ElementName="allRecordsOne" Path="Text"></Binding>
<Binding ElementName="allRecordsTwo" Path="Text"></Binding>
</MultiBinding>
</TextBox.Text>
</TextBox>
Source TextBoxes:
<TextBox x:Name="allRecordsOne" Style="{StaticResource dataEntryTextBox}" Grid.Column="0" Grid.Row="4" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
<TextBox x:Name="allRecordsTwo" Style="{StaticResource readOnlyTextBox}" Grid.Column="0" Grid.Row="5" Text="{Binding ElementName=allRecordsOne, Path=Text}" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
<TextBox x:Name="allRecordsThree" Style="{StaticResource readOnlyTextBox}" Grid.Column="0" Grid.Row="6" Text="{Binding ElementName=allRecordsOne, Path=Text}" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
I simplified your example. Note that I used Mode="OneWay" to avoid exception in ConvertBack method.
<StackPanel>
<TextBox x:Name="allRecords">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AddListRecordsConverter}">
<Binding ElementName="allRecordsOne" Path="Text" Mode="OneWay"/>
<Binding ElementName="allRecordsTwo" Path="Text" Mode="OneWay"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBox x:Name="allRecordsOne" />
<TextBox x:Name="allRecordsTwo" />
</StackPanel>
the issue with the converter is that it receives two empty strings (default value of text) as input (values) and can't handle them properly. it has to be more defensive
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double val = 0.0;
double result = 0.0;
foreach (var txt in values)
{
if (double.TryParse(txt.ToString(), out val))
result += val;
else
return "NaN";
}
return result.ToString();
}

WPF Localization: DynamicResource with StringFormat?

I am doing localization in .NET 4 with a ResourceDictionary. Does anyone have a solution for using a value with string format?
For instance, let's say I have a value with the key "SomeKey":
<ResourceDictionary ...>
<s:String x:Key="SomeKey">You ran {0} miles</s:String>
</ResourceDictionary>
Using it in a TextBlock:
<TextBlock Text="{DynamicResource SomeKey}" />
How would I combine, for example, an integer with the value of SomeKey as a format string?
You need to bind to a ViewModel.Value somehow, and then use a (nested) binding to a format string.
When you have only one value:
<TextBlock
Text="{Binding Path=DemoValue, StringFormat={StaticResource SomeKey}}" />
When you also have {1} etc then you need MultiBinding.
Edit:
When you really want to change languages in a live Form then the sensible way is probably to do all formatting in the ViewModel. I rarely use StringFormat or MultiBinding in MVVM anyway.
So, I finally came up with a solution that allows me to have format strings in my ResourceDictionary and be able to dynamically change the language at runtime. I think it could be improved, but it works.
This class converts the resource key into its value from the ResourceDictionary:
public class Localization
{
public static object GetResource(DependencyObject obj)
{
return (object)obj.GetValue(ResourceProperty);
}
public static void SetResource(DependencyObject obj, object value)
{
obj.SetValue(ResourceProperty, value);
}
// Using a DependencyProperty as the backing store for Resource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ResourceProperty =
DependencyProperty.RegisterAttached("Resource", typeof(object), typeof(Localization), new PropertyMetadata(null, OnResourceChanged));
private static void OnResourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//check if ResourceReferenceExpression is already registered
if (d.ReadLocalValue(ResourceProperty).GetType().Name == "ResourceReferenceExpression")
return;
var fe = d as FrameworkElement;
if (fe == null)
return;
//register ResourceReferenceExpression - what DynamicResourceExtension outputs in ProvideValue
fe.SetResourceReference(ResourceProperty, e.NewValue);
}
}
This class allows the value from the ResourceDictionary to be used as the format parameter in String.Format()
public class FormatStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[0] == null)
return String.Empty;
var format = (string)values[0];
var args = values.Where((o, i) => { return i != 0; }).ToArray();
return String.Format(format, args);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example Usage 1: In this example, I use the FormatStringConverter in the MultiBinding to convert its Binding collection into the desired output. If, for instance, the value of "SomeKey" is "The object id is {0}" and the value of "Id" is "1" then the output will become "The object id is 1".
<TextBlock ap:Localization.Resource="SomeKey">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource formatStringConverter}">
<Binding Path="(ap:Localization.Resource)" RelativeSource="{RelativeSource Self}" />
<Binding Path="Id" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Example Usage 2: In this example, I use a binding with a Converter to change the resource key to something more verbose to prevent key collisions. If, for instance, I have the enum value Enum.Value (displayed by default as "Value"), I use the converter to attach its namespace to make a more unique key. So the value becomes "My.Enums.Namespace.Enum.Value". Then the Text property will resolve with whatever the value of "My.Enums.Namespace.Enum.Value" is in the ResourceDictionary.
<ComboBox ItemsSource="{Binding Enums}"
SelectedItem="{Binding SelectedEnum}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock ap:Localization.Resource="{Binding Converter={StaticResource enumToResourceKeyConverter}}"
Text="{Binding Path=ap:Localization.Resource), RelativeSource={RelativeSource Self}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Example Usage 3: In this example, the key is a literal and is used only to find its corresponding value in the ResourceDictionary. If, for instance, "SomeKey" has the value "SomeValue" then it will simply output "SomeValue".
<TextBlock ap:Localization.Resource="SomeKey"
Text="{Binding Path=ap:Localization.Resource), RelativeSource={RelativeSource Self}}"/>
If you're trying to bind and format a Miles property to a 'TextBlock' you can do as follows:
<TextBlock Text="{Binding Miles, StringFormat={StaticResource SomeKey}}"/>

Unable to cast object of type 'MS.Internal.NamedObject' to BitmapImage

I am building a WPF application in which I am getting an error as
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.Windows.Media.Imaging.BitmapImage'
XAML Code:
<DataGridTemplateColumn Header="Active" Width="60">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button>
<Button.Content>
<MultiBinding Converter="{StaticResource myImageConverter}"
ConverterParameter="Active">
<Binding Path="IsClosed"/>
<Binding Path="IsChecked"/>
<Binding Path="IsActive"/>
<Binding Path="TickImage"/>
<Binding Path="CrossImage"/>
</MultiBinding>
</Button.Content>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
C# Converter Code:
public class ImageConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool isClosed = (bool)values[0];
bool isChecked = (bool)values[1];
bool isActive = (bool)values[2];
Image img = new Image();
switch ((string)parameter)
{
case "Active":
if (isClosed == true && isChecked == true)
{
if (isActive == true)
{
img.Source = (BitmapImage)values[3];
img.Stretch = Stretch.Fill;
}
else
{
img.Source = (BitmapImage)values[4];
img.Stretch = Stretch.Fill;
}
}
break;
}
return img;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
TickImage and CrossImage are properties of the ViewModel Class. They are initialized in the ViewModel constructor as shown below.
TickImage = new BitmapImage(new Uri("K:\\projects\\ContentSets\\Ownership\\SOMA\\Staging\\SOMA\\Images\\icon_tick.gif", UriKind.Absolute));
CrossImage = new BitmapImage(new Uri("K:\\projects\\ContentSets\\Ownership\\SOMA\\Staging\\SOMA\\Images\\icon_cross.gif", UriKind.Absolute));
TickImage.Freeze();
CrossImage.Freeze();
IsClosed, IsChecked and IsActive are properties of DataObject Class.
The error occurs at the 1st line of the condition if (isActive == true)
I have also tried the following XAML code:
<Button.Content>
<Image>
<Image.Source>
<MultiBinding Converter="{StaticResource myImageConverter}"
ConverterParameter="Active">
<Binding Path="IsClosed"/>
<Binding Path="IsChecked"/>
<Binding Path="IsActive"/>
<Binding Path="TickImage"/>
<Binding Path="CrossImage"/>
</MultiBinding>
</Image.Source>
</Image>
</Button.Content>
TickImage and CrossImage are simple strings in the ViewModel and with necessary changes in the Converter the same error is thrown as follows
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.String'
I'm pretty sure your bindings are incorrect. When you perform a binding inside a CellTemplate you're actually binding to the cell's DataContext rather than your view's DataContext (i.e the viewmodel).
You should try modifying your bindings to take the values from your viewmodel, like this for example:
<Binding Path="DataContext.TickImage" RelativeSource="{RelativeSource AncestorType=DataGrid}" />

Categories