WPF Binding with StringFormat-ting for float values - wpf

I know there are a lot of answers to this, but in my case it doesn't work, looks like it's something specific I'm struggling with.
I have this piece of code on my XAML side, but for some reason these text blocks show the raw float values rounded up to 2 decimal points, whereas, based on the string formatting it supposed to show up to 4 decimal points if there are. In my case I was expecting to show my Y value to show in tooltip 12.1864, but it shows 12.19. How can I make it format the way I want?
<TextBlock>
<Run Text="X: "/>
<Run Text="{Binding XValue, StringFormat={}{0:#.####}, Mode=OneWay}"/>
</TextBlock>
<TextBlock>
<Run Text="Y: "/>
<Run Text="{Binding YValue, StringFormat={}{0:#.####}, Mode=OneWay}"/>
</TextBlock>

Thanks to the hint by #KeithStein I figured it out. The issue was the type of the bound properties, which in my case where from a third party lib and was string. I could have implement a value converter, but I was lucky the third lib also has an option of the same float values not in string but in raw float type. So binding to those and keeping the same XAML code, worked well for me.

I have checked your code & it works fine as expected with 4 decimal points,
Please find code below
public string XValue { get; set; }
public string YValue { get; set; }
private void setXYValue()
{
XValue = "19.45678";
YValue = "20.12345";
OnPropertyChanged(nameof(XValue));
OnPropertyChanged(nameof(YValue));
}
<StackPanel Orientation="Vertical">
<TextBlock>
<Run Text="X: "/>
<Run Text="{Binding XValue, StringFormat={}{0:#.####}, Mode=OneWay}"/>
</TextBlock>
<TextBlock>
<Run Text="Y: "/>
<Run Text="{Binding YValue, StringFormat={}{0:#.####}, Mode=OneWay}"/>
</TextBlock>
</StackPanel>

Your code works fine for me
<Window
x:Class="BindingFormatTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingFormatTest"
SizeToContent="WidthAndHeight">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<StackPanel>
<TextBlock>
<Run Text="X: " />
<Run Text="{Binding XValue, StringFormat={}{0:#.####}, Mode=OneWay}" />
</TextBlock>
<TextBlock>
<Run Text="Y: " />
<Run Text="{Binding YValue, StringFormat={}{0:#.####}, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</Window>
public class MainViewModel
{
public double XValue { get; } = 12.345678;
public double YValue { get; } = 23.456789;
}
Try using a tool such as WPFSnoop to check that the data context and the values are really what you expected.

Related

Define ToolTip with several binding properties

This is my Dodel:
public string Name { get; set; }
public string Id { get; set; }
public string Age { get; set; }
public string Description { get; set; }
This is my ListView column:
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="textBlock"
Text="{Binding Description}"
ToolTip="{Binding Description}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
So in case i want to see in my ToolTip all my properties for example:
name + "\n" + Id + "\n" + Age + + "\n" + Description
Here is what I would have done for that:
<DataTemplate>
<DataTemplate.Resources>
<ToolTip x:Key="Tip">
<TextBlock>
<Run Text="{Binding Name}"/>
<LineBreak/>
<Run Text="{Binding Age, StringFormat='Age: {0}'}"/>
</TextBlock>
</ToolTip>
</DataTemplate.Resources>
<TextBlock Text="{Binding Description}" ToolTip="{StaticResource Tip}"/>
</DataTemplate>
I used Runs so you can Bind Name and other properties on after the other with complete customisation.
Why not add a new string type property to your Model and combine all of them in the constructor.
If you really want to do it this way, this may help you.

How to read all textbox values(Map to database fileds) and store them in Database in WPF - MVVM

I am new to WPF and MVVM. After a long invention i got to know how to retrieve data from database and bind it to a itemcontrol/listview/gridview.
But my problem i am not getting how to read bunch of textbox values and store as a new record in database.
Here is my sample code..
View
<ItemsControl ItemsSource="{Binding AllEmployees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Text="{Binding FirstName}" Margin="4"/>
<TextBox Width="100" Text="{Binding LastName}" Margin="4"/>
<TextBox Width="100" Text="{Binding Age}" Margin="4"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- For new Employee Details -->
<StackPanel>
<TextBox x:Name="FirstName"/>
<TextBox x:Name="LastName"/>
<TextBox x:Name="Age"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
My cs file is
public ObservableCollection<DataAccess.Employee> AllEmployees
{
get;
private set;
}
public EmployeeListViewModel(EmployeeRepository employeeRepository)
{
if (employeeRepository == null)
{
throw new ArgumentNullException("employeeRepository");
}
_employeeRepository = employeeRepository;
this.AllEmployees = new ObservableCollection<DataAccess.Employee>
(_employeeRepository.ListAll());
}
Now how could i store a new employee Firstname, Lastname, Age in database by reading those text boxes..
How to write function for NewEmployeeCommand event to read the textboxes( mapping of textboxes to appropriate datafileds in database) and store the data in database.
Thanks a Lot !
if you're trying to use MVVM just need to:
Create your ViewModel to contain all the properties your View needs
Bind to those properties in Xaml
For example:
public class EmployeeListViewModel
{
public ObservableCollection<Employee> AllEmployees {get;private set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int? Age {get;set;}
public ICommand NewEmployeeCommand {get;set;}
//You need to connect to this method by using a Delegate/RelayCommand see link below
public void AddNewEmployee()
{
//Add your real code here to actually insert into the db
var result = InsertEmployeeIntoDatabase(FirstName,LastName,Age);
//You probably want to add this new employee to the list now ;)
AllEmployees.Add(result);
//Now you probably want to reset your fields
FirstName = null;
LastName = null;
Age = null;
}
}
Click here for an implementation of a delegate command
And then just edit your xaml like this:
<ItemsControl ItemsSource="{Binding AllEmployees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Text="{Binding FirstName}" Margin="4"/>
<TextBox Width="100" Text="{Binding LastName}" Margin="4"/>
<TextBox Width="100" Text="{Binding Age}" Margin="4"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- For new Employee Details -->
<StackPanel>
<TextBox Text={Binding FirstName}"/>
<TextBox Text={Binding LastName}"/>
<TextBox Text={Binding Age}"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
You could pass references in the command parameter:
<StackPanel>
<TextBox x:Name="FirstName"/>
<TextBox x:Name="LastName"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}">
<Button.CommandParameter>
<x:Array Type="{x:Type TextBox}">
<x:Reference Name="FirstName"/>
<x:Reference Name="LastName"/>
</x:Array>
</Button.CommandParameter>
</Button>
</StackPanel>
Depending on what kind of event you use you case the parameter and get the values:
TextBox[] textBoxes = e.Parameter as TextBox[]; //RoutedEvent
TextBox[] textBoxes = parameter as TextBox[]; //If the executed handler provides the parameter
string firstName = textBoxes[0].Text;
string lastName = textBoxes[1].Text;
//create entry; store in DB
Via binding:
<Button.CommandParameter>
<local:MyEntry FirstName="{Binding ElementName=FirstName, Path=Text}"
LastName="{Binding ElementName=LastName, Path=Text}"/>
</Button.CommandParameter>
MyEntry entry = parameter as MyEntry;
//store in DB
you dont read the Textbox values. you need a NewEmployeeViewModel and bind the TextBoxes to the properties.
EDIT:
just create a class with INotifyPropertyChanged and the Properties you need.
public class NewEmployee : INotifyPropertyChanged
{
public string FirstName
{
get{return this._firstname;}
set{this._firstname = value;
OnPropertyChanged("FirstName");}
}
//... other properties
}
xaml
<StackPanel DataContext={Binding MyNewEmployeeProperty}>
<TextBox x:Name="FirstName" Text={Binding FirstName}/>
<TextBox x:Name="LastName" Text={Binding LastName}/>
<TextBox x:Name="Age" Text={Binding Age}/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
I got the Correct answer.
My New entry form in xmal Should be
<StackPanel>
<TextBox Text={Binding Employee.FirstName}"/>
<TextBox Text={Binding Employee.LastName}"/>
<TextBox Text={Binding Employee.Age}"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
My cs file
public class EmployeeListViewModel : INotifyPropertyChanged
{
Employee _employee;
RelayCommand _addNewEmployee;
EmployeeRepository _employeeRepository;
public Employee Employee
{
get
{
return _employee;
}
set
{
_employee = value;
OnPropertyChanged("Employee");
}
}
public void NewEmployeeCommand()
{
if(_addNewEmployee == null)
{
_addNewEmployee = new RelayCommand( param => NewEmployeeCommandExecute(),
param => NewEmployeeCommandCanExecute
);
}
}
void NewEmployeeCommandExecute()
{
_employeeRepository.Add(Employee);
_employeeRepository.Save();
}
bool NewEmployeeCommandCanExecute
{
get
{
return true;
}
}
}

Does StringFormat work with this.DataContext

I am binding a TextBlock with a collection in code-behind via this.DataContext = SellerList;
The output is correct but when i apply StringFormat, i see no result. Following is the code for TextBlock on xaml page
<TextBlock Name="dateDTKey" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Text="{Binding Path=Date, StringFormat={}{0:dd-MM-yyyy}}"
Style="{StaticResource textStyleTextBlock}"/>
The source for the Binding is a string, if detailsSellerListingTemplate is a resource you should use {StaticResource detailsSellerListingTemplate}. Also, the TextBlock doesn't need a DataContext for this Binding to work since it's using Source.
<Window.Resources>
<local:DetailsSeller x:Key="detailsSellerListingTemplate"/>
</Window.Resources>
<TextBlock Name="dateDTKey"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Text="{Binding Source={StaticResource detailsSellerListingTemplate},
Path=Date,
StringFormat={}{0:dd-MM-yyyy}}"/>
This will work if DetailsSeller looks similar to this
public class DetailsSeller
{
public DetailsSeller()
{
Date = DateTime.Now;
}
public DateTime Date
{
get;
set;
}
}
You talked about a collection but I can't see how that fits with the binding, so maybe I missunderstood something in the question
I am thinking its because you have way to many braces in your string format. try this:
StringFormat={0:dd-MM-yyyy}

WPF Treeview - Binding to a collection with different depths, and styling differently

Apologies for the long post -- Read a few threads about this, but still find it hard to implement. Basically, I have a collection of objects, defined as:
public class LibData
{
public string Name { get; set; }
ObservableCollection<LibObject> _list;
public ObservableCollection<LibObject> List { get { return _list; } }
public LibData(string name, LibDataType type)
{
this.Name = name;
_list = new ObservableCollection<LibObject>();
}
}
and the object:
public class LibObject
{
public string Name { get; set; }
public LibObject(string name)
{
this.Name = name;
}
}
My main problem is in the XAML, and styling this TreeView. I need to have a specific style for a "root" item, and a specific style for a "leaf". Thing is, that one item in the bound list is "Root->Leaf", and another is "Root->Child->Leaf".
I tried this:
<TreeView x:Name="myTree" ItemsSource="{x:Static local:myDataList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=List}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<CheckBox IsChecked="True" Content="HeaderCheckbox"/>
</StackPanel>
</Grid>
<HierarchicalDataTemplate.ItemTemplate >
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
This obviously works fine for the "Root->Leaf" item, but not for the "Root-Child-Leaf".
The XAML implementation seems to be more of a "hard coded" solution, where I know that the item layout is always "Root->Leaf" - how do I make this dynamic?
Again, I have seen solutions to different levels (including using converters), but the problem i'm having is that I need specific styles to root and leaf and nothing for levels in between.
I'm wondering if I'm looking at this completely wrong ...
Easy way to do this. Pull the DataTemplates out of the TreeView and put them in the resources section. Specify the DataType property for each DataTemplate, but do not include a key. The ItemTemplateSelector (a property of type DataTemplateSelector) on the TreeView will do the magic of using whichever DataTemplate suits the correct item type.
Create a HierarchicalDataTemplate for types Root and Child, and a DataTemplate for type Leaf.
Something like this:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:Leaf}">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=SomeValue}"/>
</StackPanel>
</Grid>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Child}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Child " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Root}"
ItemsSource="{Binding Children}">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Root " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView x:Name="myTree" ItemsSource="{Binding}" />
</Grid>

WPF: LineBreak enable/disable dynamically

i would like to make the LineBreak element inside of that TextBlock controllable by the user in preferences to Enable/Disable it being there
<TextBlock Style="{StaticResource TextBlockStyle}" Width="130">
<TextBlock.Inlines>
<Run Text="{Binding Path=Name}" FontWeight="Bold" Foreground="#FF2A4D9E" />
<Run Text="{Binding Path=Price}" FontWeight="Bold" />
<LineBreak />
<Run Text="{Binding Path=Quantity}" Foreground="#99000000" />
</TextBlock.Inlines>
</TextBlock>
I don't believe there is any way in a FlowDocument to make a LineBreak not really break except to take it out. Your choices are to switch to using WPF layout or to use an attached property to switch between a LineBreak and an empty Run.
Using WPF layout
You may consider using WPF layout instead. Something like this:
<DataTemplate x:Key="Layout1">
<DockPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="#FF2A4D9E" />
<TextBlock Text="{Binding Price}" FontWeight="Bold" />
<TextBlock Text="{Binding Quantity}" Foreground="#99000000" />
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="Layout2">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="#FF2A4D9E" />
<TextBlock Text="{Binding Price}" FontWeight="Bold" />
</DockPanel>
<TextBlock Text="{Binding Quantity}" Foreground="#99000000" />
</DockPanel>
</DataTemplate>
Now you can easily switch between layouts just by switching DataTemplates.
Automatically removing LineBreaks using bindings
If you want to "hide" the LineBreak via a binding you can do it with an attached "BecomeLineBreak" property that, when applied to an empty Run and set true, removes it and replaces it with a LineBreak.
Like magic you now have the ability to write:
<Run my:LineBreakSwitcher.BecomeLineBreak="{Binding SomeCondition}" />
And your Run will turn into a LineBreak any time the SomeCondition property is true.
Here is the code:
public class LineBreakSwitcher : DependencyObject
{
public static bool GetBecomeLineBreak(DependencyObject obj) { return (bool)obj.GetValue(BecomeLineBreakProperty); }
public static void SetBecomeLineBreak(DependencyObject obj, bool value) { obj.SetValue(BecomeLineBreakProperty, value); }
public static readonly DependencyProperty BecomeLineBreakProperty = DependencyProperty.RegisterAttached("BecomeLineBreak", typeof(bool), typeof(LineBreakSwitcher), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var oldElement = (Inline)obj;
var newElement = (bool)e.NewValue ? (Inline)new LineBreak() : new Run();
newElement.SetBinding(BecomeLineBreakProperty, oldElement.GetBindingExpression(BecomeLineBreakProperty).ParentBindingBase);
var parent = (Paragraph)oldElement.Parent;
parent.Inlines.InsertBefore(oldElement, newElement);
parent.Inlines.Remove(oldElement);
}
});
How it works: When BecomeLineBreak becomes true on a Run, a new LineBreak is created, the BecomeLineBreak binding is copied across, the LineBreak is inserted before the Run, then the Run is removed. When BecomeLineBreak become false, a new Run is created and the whole process happens in reverse.
This is what you want (100% XAML):
<TextBlock Text="{Binding Text, ElementName=MyContainer}"
FontWeight="Bold" FontSize="14" Name="TextBlockA" />
<TextBlock Name="TextBlockB">
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text.Length,
ElementName=TextBlockC}" Value="0">
<Setter Property="TextBlock.Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
<LineBreak />
</TextBlock>
<TextBlock Text="{Binding SubText, ElementName=MyContainer}"
FontWeight="Normal" FontSize="12" Name="TextBlockC" />

Resources