Bind a formula to a TextBox - wpf

I'm doing a little program that needs to calculate the thickness of a book using its page count.
Please note that I'm almost completely new to programing. And this is my first time on ths website.
Here's the XAML.
<StackPanel VerticalAlignment="Center" Margin="25 5 0 10">
<Slider Maximum="800" TickPlacement="BottomRight" TickFrequency="1"
IsSnapToTickEnabled="True" DockPanel.Dock="Right" x:Name="slNumPages"
Margin="0" LargeChange="16" SmallChange="2" Value="200" Minimum="16"
BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}" Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness" Text="..."/>
</WrapPanel>
How can I bind the TextBlock tbApproxThickness to a formula where the value of slider slNumPages gets multiplied by constant 0.0252?

here you go
<StackPanel VerticalAlignment="Center"
Margin="25 5 0 10"
xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<l:ApproxThicknessConverter x:Key="ApproxThicknessConverter" />
</StackPanel.Resources>
<Slider Maximum="800"
TickPlacement="BottomRight"
TickFrequency="1"
IsSnapToTickEnabled="True"
DockPanel.Dock="Right"
x:Name="slNumPages"
Margin="0"
LargeChange="16"
SmallChange="2"
Value="200"
Minimum="16"
BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}"
Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness"
Text="{Binding Value,ElementName=slNumPages,Converter={StaticResource ApproxThicknessConverter}}" />
</WrapPanel>
</StackPanel>
following are the changed for the same
<l:ApproxThicknessConverter x:Key="ApproxThicknessConverter" />
Text="{Binding Value,ElementName=slNumPages,Converter={StaticResource ApproxThicknessConverter}}"
converter class
namespace CSharpWPF
{
class ApproxThicknessConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value * 0.0252;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

You may need to refer to the Data Binding Overview page on MSDN to understand this answer. One simple solution would be to data bind a double property to the Slider.Value property and then reference this property in a formula property that does the calculation. Try something like this:
public double SliderValue
{
get { return sliderValue; }
set
{
sliderValue = value;
NotifyPropertyChanged("SliderValue"); // Implement INotifyPropertyChanged
FormulaValue = sliderValue * 0.0252;
}
}
public double FormulaValue { get; set; } // Implement INotifyPropertyChanged
...
<Slider Maximum="800" TickPlacement="BottomRight" TickFrequency="1"
IsSnapToTickEnabled="True" DockPanel.Dock="Right" x:Name="slNumPages"
Margin="0" LargeChange="16" SmallChange="2" Value="{Binding SliderValue}"
Minimum="16" BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}" Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness" Text="{Binding FormulaValue}" />
</WrapPanel>
Please bear in mind that you will need to implement the INotifyPropertyChanged Interface on these properties to make this work. You will also need to set the DataContext of the Window properly. Having done that, each time the Slider is moved, the property will automatically update the FormulaValue.

Related

WPF Combobox always displays icon of the first element

I have a combobox in a custom window where I display a language short name its corresponding icon
<ComboBox SelectionChanged="LanguagesListBoxSelectionChanged" WindowChrome.IsHitTestVisibleInChrome="True"
Visibility="{TemplateBinding IsLanguageSwitchComboBoxVisible}"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged, Path=Languages}"
IsEnabled="True" DockPanel.Dock="Right" Width="90" Height="28"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= SelectedLanguage}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<TextBlock FontSize="24" VerticalAlignment="Center" Text="{Binding LanguageName}" Margin="0 0 4 0"></TextBlock>
<Image Height="24" Width="24">
<Image.Source>
<BitmapImage UriSource="{Binding LanguagePicture, Converter={StaticResource DebugDummyConverter}}" />
</Image.Source>
</Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Custom window:
public class CustomTitleBarWindow : Window
{
public CustomTitleBarWindow()
{
Languages = new List<LanguageModel>()
{
new() {Id="ru", LanguageName = "RU", LanguagePicture = "../../Images/ru.ico" },
new() {Id="en", LanguageName = "EN", LanguagePicture = "../../Images/en.ico" }
};
SelectedLanguage = Languages.First();
}
public static readonly DependencyProperty SelectedLanguageProperty =
DependencyProperty.Register("SelectedLanguage", typeof(LanguageModel), typeof(CustomTitleBarWindow), new PropertyMetadata(null));
public LanguageModel SelectedLanguage
{
get => (LanguageModel)GetValue(SelectedLanguageProperty);
set => SetValue(SelectedLanguageProperty, value);
}
...
Item list displays correctly.
The issue is when I select any item, the combobox displays a proper text ("EN" or "RU") but the icon displayed is the one at "../../Images/ru.ico" path.
If I put "../../Images/en.ico" into the list first then the "english" icon will be displayed no matter what item was selected.
Solved:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<TextBlock FontSize="24" VerticalAlignment="Center" Text="{Binding LanguageName}" Margin="0 0 4 0"></TextBlock>
<Image Height="24" Width="24" Source="{Binding LanguagePicture, Converter={StaticResource ImageConverter}}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
And an ImageConverter
public class ImageConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return new BitmapImage(new Uri(value.ToString(), UriKind.Relative));
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The problem is that BitmapImage needs a value immediately whereas binding provides it later.

WPF nullreference exception thrown on Button when value converter in prior Label is in place

TLDR:
A Nullreference exception is thrown when XAML parsing any button element when another unrelated element has databinding and value converter. When the buttons are commented out, or the databinding is removed the form works.
I have a WPF UserControl with a list box in it which has a DataTemplate with multiple controls in it. I also have a bool to visibility value converter which I use in different locations in the control. I added a new static reference of the converter to the control (different bool to visibility values) and bind it to a label and suddenly the app crashes upon loading the control.
I remove the binding and all is well again. The converter is not at fault though; I put break points in its constructor as well as the convert methods, and it never reaches it. The exception is in the parsing of the XAML, not at the label, but at the first button declared, which is 100% unrelated to the label. If I remove the value converter binding from the label, the XAML parses correctly and the button has no issues.
However, to complicate things, if I comment out that button and every other button in the XAML, it also parses correctly and the value converter works without a problem.
What am I missing?
XAML:
<UserControl x:Class="Customer_Management.OpportunityControl"
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"
mc:Ignorable="d"
xmlns:l="clr-namespace:Customer_Management"
d:DesignHeight="300" d:DesignWidth="300" MaxHeight="200" BorderBrush="DarkGray" BorderThickness="1" x:Name="ucOpp">
<UserControl.Resources>
<l:NullToVisibilityConverter NullValue="Hidden" NonNullValue="Visible" x:Key="NullToHidden"></l:NullToVisibilityConverter>
<l:BoolToVisibilityConverter TrueValue="Visible" FalseValue="Hidden" x:Key="TrueToVisible"></l:BoolToVisibilityConverter>
<l:BoolToVisibilityConverter TrueValue="Hidden" FalseValue="Visible" x:Key="FalseToVisible"></l:BoolToVisibilityConverter>
</UserControl.Resources>
<ScrollViewer>
<ListBox Name="lbxOpps" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="DarkGray" BorderThickness="1">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Path=Opportunity.Name}" Margin="0,1,3,1"></TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Opportunity.Amount, StringFormat=\{0:C\}}" Margin="0,1,3,1"></TextBlock>
<Button Name="btnFinishOrder" Click="btnFinishOrder_Click">Finish Order</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,1,3,1">Invoice #</TextBlock>
<TextBox Name="tbxInvoiceNumber" Text="{Binding Path=InvoiceNumber}"></TextBox>
</StackPanel>
<ListBox ItemsSource="{Binding Path=Batches}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label FontWeight="Bold" Visibility="{Binding Path=IsOcc, Converter={StaticResource TrueToVisible}}">OCC #:</Label>
<ComboBox Margin="0,0,0,0" Name="cboLicense" SelectedValue="{Binding Path=SLicense}" DisplayMemberPath="LicenseID"
SelectedValuePath="LicenseID" ItemsSource="{Binding ElementName=ucOpp, Path=Licenses}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cboLicense}" Click="Button_ClearContorl">X</Button>-->
<Label Margin="0,0,3,0" >R #:</Label>
<!--<Button ToolTip="Click to change" Name="btnLicFile" Click="Button_Click" >LIC File</Button>-->
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=btnLicFile}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
<Label Margin="0,0,3,0" >P #:</Label>
<ComboBox Margin="0,0,0,0" Name="cbxPNum" SelectedValue="{Binding Path=PNum}" DisplayMemberPath="Name"
SelectedValuePath="Id" ItemsSource="{Binding ElementName=ucOpp, Path=Nums}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cbxPNum}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
<Label Margin="0,0,3,0" >U #:</Label>
<ComboBox Margin="0,0,0,0" Name="cbxUNum" SelectedValue="{Binding Path=UNum}" DisplayMemberPath="Name"
SelectedValuePath="Id" ItemsSource="{Binding ElementName=ucOpp, Path=Nums}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cbxUNum}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
<Label HorizontalAlignment="Stretch" Background="LawnGreen" FontSize="24" Opacity="0.8" VerticalAlignment="Stretch" Visibility="{Binding Path=IsProcessing, Converter={StaticResource TrueToVisible}}"></Label>
<Label HorizontalAlignment="Center" FontSize="24" VerticalAlignment="Center" Visibility="{Binding Path=IsProcessing, Converter={StaticResource TrueToVisible}}">Processing...</Label>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
ValueConverter:
public sealed class BoolToVisibilityConverter : IValueConverter
{
public Visibility TrueValue { get; set; }
public Visibility FalseValue { get; set; }
public BoolToVisibilityConverter()
{
// set defaults
TrueValue = Visibility.Visible;
FalseValue = Visibility.Collapsed;
}
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (!(value is bool))
return null;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (Equals(value, TrueValue))
return true;
if (Equals(value, FalseValue))
return false;
return null;
}
}

WPF binding TextBox and ObservableDictionary<Int64,String> (display String by Id)

Each item of my Employee's list has Post property. This property is Int64 type. Also, I have some ObservableDictionary<Int64,String> as static property. Each Employe must display the String value by its key.
DataTemplate for Employe item (I deleted the superfluous):
<DataTemplate x:Key="tmpEmploye">
<Border BorderThickness="3" BorderBrush="Gray" CornerRadius="5">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Post}"/>
</StackPanel>
</Border>
</DataTemplate>
But this code displayed the Int64 value, not the String. String for getting static dictionary:
"{Binding Source={x:Static app:Program.Data}, Path=Posts}"
I know how solve it problem for ComboBox, but I don't know for TextBlock. For ComboBox I wrote it (it is works fine):
<ComboBox x:Name="cboPost" x:FieldModifier="public" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="2" Grid.ColumnSpan="2"
ItemsSource="{Binding Source={x:Static app:Program.Data}, Path=Posts}" DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=Post, Mode=TwoWay}">
</ComboBox>
But how can I solve it for TextBlock?
mmmmm, I'm sure I have developed something for this scenario before but I can't remember or find anything related!
IMO you can use a converter, so you pass your Post (Int64) to the converter and it returns the string value from the dictionary, although it must be a better solution.
[ValueConversion(typeof(Int64), typeof(string))]
public class PostToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// validation code, etc
return (from p in YourStaticDictionary where p.Key == Convert.ToInt64(value) select p.Value).FirstOrDefault();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
}
}
XAML:
<Window ...
xmlns:l="clr-namespace:YourConverterNamespace"
...>
<Window.Resources>
<l:PostToStringConverter x:Key="converter" />
</Window.Resources>
<Grid>
<TextBlock Text="{Binding Post, Converter={StaticResource converter}}" />
</Grid>
</Window>

Binding a Silverlight TabControl to a complex object using a converter

I am trying to bind my data to a tab control. I have got the headers displaying fine but I'm not sure how I get the content of the tabs to bind correctly based on my item template shown below.
I think I'm missing something when I'm creating the tab item but I'm not sure how to bind my MyCustomObject to each of the TabItem's.
XAML:
<sdk:TabControl ItemsSource="{Binding Singles,Converter={StaticResource TabConverter}}">
<sdk:TabControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</sdk:TabControl.ItemsPanel>
<sdk:TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Converter={StaticResource RoundNumberConverter}}" Margin="2" />
<ListBox x:Name="Matches" ItemsSource="{Binding}" Margin="2">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Converter={StaticResource SeedingConverter}, ConverterParameter=true}" Margin="2" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Converter={StaticResource SeedingConverter}, ConverterParameter=false}" Margin="2" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Player1Name}" Margin="2" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Player2Name}" Margin="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</sdk:TabControl.ItemTemplate>
</sdk:TabControl>
Converter:
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IEnumerable<IGrouping<string, MyCustomObject>> source = value as IEnumerable<IGrouping<string, MyCustomObject>>;
if (source != null)
{
var controlTemplate = (ControlTemplate)parameter;
List<TabItem> result = new List<TabItem>();
foreach (IGrouping<string, MyCustomObject> tab in source)
{
result.Add(new TabItem()
{
Header = tab.Key,
DataContext = tab //not sure this is right?
});
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You're most of the way there, the following works for me:
Strip out your DataTemplate and drop its contents straight into a new UserControl, for the example here, lets call it MatchesView.
Then in your TabConverter, amend the contents of the foreach loop to something like the following:
TabItem tabitem = new TabItem();
tabitem.Header = tab.Key;
MatchesView tabview = new MatchesView();
tabview.DataContext = parameter;
tabitem.Content = tabview;
result.Add(tabitem);
Note: this requires that you pass your ViewModel to the TabConverter as a parameter, eg:
<sdk:TabControl SelectedItem="{Binding YourSelectedObject}" ItemsSource="{Binding YourCollectionObject, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource YourViewModel}, Mode=TwoWay}" />
Then, as you have your ViewModel in each instance of the new control, adjust your binding accordingly!
Note that the trick is that you have a separate binding for the single instance of the Selected object
checking your problem I think there is no support for that. The WPF's TabItem contains ItemTemplate & ContentTemplate. The first is the template for the Header, the second is the template for the "body". In silverlight we still have no ContentTemplate. Haven't seen an official statement yet, but this guy says it won't be supported till SL5.

Refreshing a binding that uses a value converter

I have a WPF UI that is bound to an object. I'm using a ValueConverter to convert a property to a specific image by a business rule:
public class ProposalStateImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var proposal = value as Proposal;
var basePath = "pack://application:,,,/ePub.Content;component/Images/General/Flag_{0}.png";
string imagePath;
if(proposal.Invoice != null)
{
imagePath = string.Format(basePath, "Good");
}
else
{
imagePath = string.Format(basePath, "Warning");
}
var uri = new Uri(imagePath);
var src = uri.GetImageSource(); //Extention method
return src;
}
}
The element is a TreeView where the image is on the 2nd level:
<TreeView x:Name="tree"
ItemsSource="{Binding People}"
SelectedItemChanged="OnTreeItemChanged">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dmn:Person}"
ItemsSource="{Binding Proposals}">
<StackPanel Orientation="Horizontal" ToolTip="{Binding Path=Fullname}" Margin="3">
<Image Margin="5,0,5,0" Width="16" Height="16" Source="pack://application:,,,/ePub.Content;component/Images/General/Person_Active.png" />
<TextBlock Text="{Binding Path=Firstname}" />
<TextBlock Text="{Binding Path=Lastname}" Margin="5,0,0,0" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dmn:Proposal}">
<StackPanel Orientation="Horizontal" Margin="3">
<Image x:Name="invoiceImage" Width="16" Height="16" Margin="5,0,5,0" Source="{Binding, Converter={StaticResource ProposalStateImageConverter}, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding DeliveryDate, Converter={StaticResource textCulturedDateConverter}}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
It is working fine, but later, when the object's state changes, I want to refresh the image and make the value converter reevaluate. How is this possible?
It looks like you're only using a single value inside the converter and you're just doing a simple switch between two values so you could instead just do this directly in XAML with a trigger. This method also switches to a Binding against the Invoice property so that any change notifications for that property will cause the Trigger to update.
<HierarchicalDataTemplate >
<StackPanel Orientation="Horizontal" Margin="3">
<Image x:Name="invoiceImage" Width="16" Height="16" Margin="5,0,5,0" Source="good.png"/>
<TextBlock ... />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Invoice}" Value="{x:Null}">
<Setter TargetName="invoiceImage" Property="Source" Value="warning.png"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
Assuming you can't use INotifyPropertyChanged because you're binding to the whole object, you need to call BindingExpression.UpdateTarget.
The slight subtlety is in getting hold of the binding expression. This requires you to have a fairly intimate knowledge of the view: as far as I know, the only way to do this is to call BindingOperations.GetBindingExpression, passing the control and property whose binding you want to update, e.g.:
BindingOperations.GetBindingExpression(myImage, Image.SourceProperty).UpdateTarget();

Resources