Please, can some body tell me how to get my double value formatted like "0.0" from code-behind, like this:
Binding b = new Binding(DoubleValue);
b.StringFormat = "????";
In xaml it works just like that "0.0"...
What about this?
b.StringFormat = "{0:F1}";
See the documentation of StringFormat and also Standard Numeric Format Strings and Custom Numeric Format Strings.
EDIT: Just to make clear how a binding would be created and assigned (to the Text property of an imaginary TextBlock named textBlock) in code:
public class ViewModel
{
public double DoubleValue { get; set; }
}
...
var viewModel = new ViewModel
{
DoubleValue = Math.PI
};
var binding = new Binding
{
Source = viewModel,
Path = new PropertyPath("DoubleValue"),
StringFormat = "{0:F1}"
};
textBlock.SetBinding(TextBlock.TextProperty, binding);
Alternatively:
var binding = new Binding
{
Path = new PropertyPath("DoubleValue"),
StringFormat = "{0:F1}"
};
textBlock.DataContext = viewModel;
textBlock.SetBinding(TextBlock.TextProperty, binding);
Related
Given a XAML-declared data template:
<DataTemplate x:Key="MyDataTemplate">
<TextBlock Text="{Binding DataValue}"/>
</DataTemplate>
And a class:
public class Model
{
public string DataValue
{
get { return "TheDataValue"; }
}
}
The following code does not do what I need:
var model = new Model();
var template = FindResource("MyDataTemplate") as DataTemplate;
var textBlock = template.LoadContent() as TextBlock;
textBlock.DataContext = model;
Eventually the binding from the DataTemplate gets resolved and the Text of the TextBlock shows "TheDataValue". But it does not happen quickly enough for some more code that needs to inspect the property. A breakpoint immediately after the last line of code shows textBlock.Text as having a value of "".
I have tried textBlock.UpdateTarget() and textBlock.InvalidateProperty(TextBlock.TextProperty) but neither seem to help.
Clearing and re-applying the databinding for the property on the dependency object seemed to do the trick:
public static void Rebind(DependencyObject target, DependencyProperty property)
{
var binding = System.Windows.Data.BindingOperations.GetBinding(target, property);
if(binding != null)
{
System.Windows.Data.BindingOperations.ClearBinding(target, property);
System.Windows.Data.BindingOperations.SetBinding(target, property, binding);
}
}
Calling that on the textBlock immediately after setting the DataContext immediately causes the binding to evaluate and property queries then return the expected, post-binding value "TheDataValue".
i am trying to binding a very simple property to a TextBlock, but I have to do all in code-behind (C#).
What i am trying to do is:
public string SomeText { get; set; }
And after I try the Binding on TextBlock:
Binding myBinding = new Binding(SomeText);
myTextBlock.SetBinding(TextBlock.TextProperty, myBinding);
How do I keep the Text property of the TextBlock the same of the Property SomeText.
Use BindingOperations
Binding binding = new Binding();
binding.Path = new PropertyPath("SomeText");
binding.Source = sourceObject; // view model?
BindingOperations.SetBinding(theTextBlock, TextBlock.TextProperty, binding);
I builded a RibbonGroupBox like this in a C# file:
public class TextControl : RibbonGroupBox
{
public TextControl()
{
const double widthOfComboBoxes = 150;
Binding fontsBinding = new Binding();
fontsBinding.Source = (TextControlVM)DataContext;
fontsBinding.Path = new System.Windows.PropertyPath("Fonts");
fontsBinding.Mode = BindingMode.TwoWay;
Binding fontSizeBinding = new Binding();
fontSizeBinding.Source = (TextControlVM)DataContext;
fontSizeBinding.Path = new System.Windows.PropertyPath("FontSize");
fontSizeBinding.Mode = BindingMode.TwoWay;
/* Combobox for the fonts (Arial, etc.) */
Fluent.ComboBox fontCombo = new Fluent.ComboBox();
fontCombo.SetBinding(Fluent.ComboBox.ItemsSourceProperty, fontsBinding);
fontCombo.SelectedItem = ((TextControlVM)DataContext).DefaultFont;
fontCombo.Width = widthOfComboBoxes;
this.AddChild(fontCombo);
/* Combobox for the fontsizes */
Fluent.ComboBox fontSizeCombo = new Fluent.ComboBox();
fontSizeCombo.SetBinding(Fluent.ComboBox.ItemsSourceProperty, fontSizeBinding);
fontSizeCombo.SelectedItem = ((TextControlVM)DataContext).DefaultFontSize;
fontSizeCombo.Width = widthOfComboBoxes;
this.AddChild(fontSizeCombo);
}
}
I furthermore have a viewmodel (TextControlVM) that contains Properties for Fonts, FontSize, DefaultFont and DefaultFontSize.
When I now use this in another module like this, the DataContext in the above example is null:
<Fluent:RibbonTabItem Header="Export">
<TextControl DataContext="{Binding DataContext.TextControl}"/>
</Fluent:RibbonTabItem>
When I build the RibbonGroupBox with XAML code everything works fine, so I want to do what XAML automatically does. How can I do that?
Background: I want to use the RibbonGroupBox in several modules. That is why I build it with C#-Code, so that I can access it dynamically. The DataContext will change dependend on the call.
The DataContext is implied in a binding automatically, so you are essentially binding to RibbonTabItem.DataContext.DataContext.TextControl, which doesn't exist
To bind to RibbonTabItem.DataContext.TextControl, simply leave the extra DataContext out of the binding
<Fluent:RibbonTabItem Header="Export">
<TextControl DataContext="{Binding TextControl}"/>
</Fluent:RibbonTabItem>
I'm using MVVM and each View maps to a ViewModel with a convention. IE
MyApp.Views.MainWindowView
MyApp.ViewModels.MainWindowViewModel
Is there a way to remove the DataTemplate and do it in C#? with some sort of loop?
<DataTemplate DataType="{x:Type vm:MainWindowViewModel}">
<vw:MainWindowView />
</DataTemplate>
So basically, you need to create data templates programmatically... That's not very straightforward, but I think you can achieve that with the FrameworkElementFactory class :
public void AddDataTemplateForView(Type viewType)
{
string viewModelTypeName = viewType.FullName + "Model";
Type viewModelType = Assembly.GetExecutingAssembly().GetType(viewModelTypeName);
DataTemplate template = new DataTemplate
{
DataType = viewModelType,
VisualTree = new FrameworkElementFactory(viewType)
};
this.Resources.Add(viewModelType, template);
}
I didn't test it, so a few adjustments might be necessary... For instance I'm not sure what the type of the resource key should be, since it is usually set implicitly when you set the DataType in XAML
Thanks Thomas, using your code i've done this.
You need to use the DataTemplateKey when adding the resoures :D
private void AddAllResources()
{
Type[] viewModelTypes = Assembly.GetAssembly(typeof(MainWindowViewModel)).GetTypes()
.Where(t => t.Namespace == "MyApp.ViewModels" && t.Name.EndsWith("ViewModel")).ToArray();
string viewName = null;
string viewFullName = null;
foreach (var vmt in viewModelTypes)
{
viewName = vmt.Name.Replace("ViewModel", "View");
viewFullName = String.Format("MyApp.Views.{0}, MyApp", viewName);
DataTemplate template = new DataTemplate
{
DataType = vmt,
VisualTree = new FrameworkElementFactory(Type.GetType(viewFullName, true))
};
this.Resources.Add(new DataTemplateKey(vmt), template);
}
}
I want to change the foreground color of cells that hold negative numbers, but I don't know how to specify the DataTrigger that would let me. I'm using something like this:
<Style x:Key="NumberCellStyle" BasedOn="{StaticResource CellStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResourceExtension SignConverter}}" Value="-1">
<Setter Property="TextBlock.Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
But in the SignConverter converter I get the whole ViewModel instead of the numeric value I want to convert. I want this to work across the app, without me needing to specify the correct Path for each binding.
Thank you very much!
Better way, write a custom column.
The code follows for anyone that's in the same situation:
public class DataGridDecimalColumn : DataGridTextColumn
{
Binding foregroundBinding;
DecimalBrushConverter brushConverter = new DecimalBrushConverter {
NegativeBrush = Brushes.Red,
PositiveBrush = Brushes.Black,
ZeroBrush = Brushes.Black,
};
protected override FrameworkElement
GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
element.SetBinding(TextBlock.ForegroundProperty, GetForegroundBinding());
return element;
}
Binding
GetForegroundBinding()
{
if(foregroundBinding == null) {
var binding = (Binding)Binding;
foregroundBinding = new Binding {
Path = binding.Path,
Converter = BrushConverter,
};
}
return foregroundBinding;
}
public DecimalBrushConverter
BrushConverter
{
get { return brushConverter; }
set { brushConverter = value; }
}
}
DecimalBrushConverter simple takes a decimal? and converts it to one of the specified brushes depending on its value.
What control are you applying the style too? It sounds like whatever you are applying it to doesn't have any specific bindings set for itself, so it is just inheriting its parents' value, which ends up being your ViewModel instance.
Update: Based on the comment, I think that you need to specify a Path in the Binding expression of the style. Since no path is specified, it just uses the current DataContext, which ends up being the entire ViewModel instance.
OK, I didn't find a way to solve my original problem, but I'll work around it by using a DataGridTemplateColumn with templates that correctly set the Foreground color depending on the value that's bind to them.
How would you get this code to read the IsSelected property of the DataGrid itself? I've tried the following code but can't work out how to get the bool value into the ConverterParameter, where the DecimalBrushConverter reads the parameter and provides the SelectedBrush if isSelected==true.
public class DataGridDecimalColumn : DataGridTextColumn
{
private readonly DecimalBrushConverter _brushConverter = new DecimalBrushConverter
{
NegativeBrush = Brushes.Red,
PositiveBrush = Brushes.Black,
ZeroBrush = Brushes.Black,
SelectedBrush = Brushes.White
};
private Binding _foregroundBinding;
private DecimalBrushConverter BrushConverter
{
get { return _brushConverter; }
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem) as TextBlock;
if (element != null)
element.SetBinding(TextBlock.ForegroundProperty, GetForegroundBinding());
return element;
}
private Binding GetForegroundBinding()
{
if (_foregroundBinding == null)
{
var binding = (Binding) Binding;
var bindingToRow = new Binding
{
Path = new PropertyPath("IsSelected"),
RelativeSource=new RelativeSource(RelativeSourceMode.FindAncestor,typeof(DataGridRow),1)
};
_foregroundBinding = new Binding
{
Path = binding.Path,
Converter = BrushConverter,
ConverterParameter = bindingToRow
};
}
return _foregroundBinding;
}
}