How to adjust width of column to content? - wpf

I have a column in a datagrid which I am setting the width of the textblck with a multivalue converter. But the width of the column is not modified. I would like that the width column adjust to the content of the texblock.
The column is this:
<DataGridTextColumn Header="Columna2" Binding="{Binding}" Width="AUTO">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Background" Value="Orange"/>
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{StaticResource MiMultiValueConverter}">
<Binding Path="UseConverterIsChecked" Source="{StaticResource vm}"/>
<Binding Path="WidthColumn2" Source="{StaticResource vm}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
I know that the textblock is resize correctly because I set a background to see which is the real size of the textblock.
This is the original size:
And this is when the converter set the value of the textblock:
How could I adjust the size of the column?
Thanks.
EDIT:
If I use horizontal stretch as is advised in some comment, I get this result:
EDIT:
This is the code MiMultiValueConverter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
bool miBlUsarConverter = (bool)values[0];
double miDbAncho = (double)values[1];
return (miBlUsarConverter) ? miDbAncho : 200;
}

You're setting the width of the elements contained inside the DataGrid column, instead of the actual column. The proper way of resizing your column would be to set the width property of the DataGridTextColumn itself:
<DataGridTextColumn Header="Columna2" Binding="{Binding}" Width="1*">
In your case, you'll want to replace it with your binding from above.
For more information, here is the official doc: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/controls/sizing-options-in-the-datagrid-control?view=netframeworkdesktop-4.8

Related

Whis is the column not resize?

I have a datagrid and I want to set the width of a column according to some values, so I am trying to use a multibinding in this way:
<DataGridTextColumn.Width>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.MyProperty" />
<Binding Source="0"/>
</MultiBinding>
</DataGridTextColumn.Width>
The converter is fired, but the width is not changed according to de value it returns.
However, I have a similiar converter to set the visibility and it works as expected:
<DataGridTextColumn.Visibility>
<MultiBinding Converter="{StaticResource MyConverterVisibilityMultiValueConverter}">
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.NombreProveedor" />
<Binding Source="0"/>
</MultiBinding>
</DataGridTextColumn.Visibility>
Why does it work with the visibivilty but not with the width?
Thanks.
EDIT:
I have tried setting the width of the textblock inside the column in this way:
Value converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//I have tried both ways, DataGridLength and return 20
//return new DataGridLength(20, DataGridLengthUnitType.SizeToHeader);
return 20;
}
Xaml: Option 1, setting the width directly, it works
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Width" Value="20"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
Xaml: option 2: trying with value converter, it doesn't work
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}" >
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.Property1" />
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.Property2" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
The converter is rised and it returns 20, but the column doesn't take this value.
Ensure that the converter returns a DataGridLength value:
return new DataGridLength(100, DataGridLengthUnitType.Pixel);

Style DataTriggers working at design time, but not at runtime - why?

(before anyone asks, the title is right: I have code working only at design time in Blend, while the most common by far would be the opposite :o )
While designing a DataTemplate in Expression Blend, I can see my DataTriggers working fine, my sample ViewModel generates a random value for the level of a battery, and both border width and background color display accordingly, either in the BatteryLevelTemplate itself, and another panel containing a lot of devices with their respective (random) battery level, with a design-time DataContext.
Here is a screenshot from Expression Blend:
And here a screenshot from the running application. Notice that, while both use exactely the same class as DataContext (but as design time in Blend), at runtime only the default RedBattery color setter is applied, even if the value itself (which also affects width) varies:
And here are the relevant code parts:
<Border.Width>
<MultiBinding Converter="{StaticResource NormalValueConverter}" FallbackValue="10">
<Binding Path="NívelBateria"/>
<Binding Path="ActualWidth" ElementName="BatteryChargeContainer"/>
</MultiBinding>
</Border.Width>
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryRed}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding NívelBateria, Converter={StaticResource ValorMaiorQue}, ConverterParameter=0.25}" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryOrange}"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding NívelBateria, Converter={StaticResource ValorMaiorQue}, ConverterParameter=0.5}" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryYellow}"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding NívelBateria, Converter={StaticResource ValorMaiorQue}, ConverterParameter=0.75}" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{StaticResource BatteryGreen}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
============
<DockPanel x:Name="PainelSetupsSensores" Background="#FFB8E6E8"/>
<DockPanel x:Name="PainelSensoresDisponiveis" Background="#FFF0F0F0"
Grid.RowSpan="2" Grid.Column="1"
DataContext="{Binding ReceiverAtivo}"
d:DataContext="{d:DesignInstance Type=local:ReceiverSimulado, IsDesignTimeCreatable=True}">
<ScrollViewer>
<ItemsControl ItemsSource="{Binding Sensores}" Margin="10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</DockPanel>
====================
class ValorMaiorQue : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double valor = (double)value;
double limite = double.Parse((string)parameter);
return valor > limite;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
UPDATE (using the valuable Snoop tip by Contango):
I "snooped" the BatteryCharge (Border) element, and found out an interesting thing:
The Width property, which is affected by a Multi Value Element Binding, is working fine and displays in a "Local", green-shaded row;
On the other hand, the Background property, which is not working, displays unsurprisingly as Style with the default red value. This one is not being "DataTriggered".
My doubt now is how I am supposed to use Snoop (or anything else) to find out why the DataTrigger is not being applied.
I discovered the problem "accidentally", and here goes the explanation:
I installed Snoop, TriggerTracing and also WPF Inspector to check properties applied by my DataTriggers, and found out the comparison provided by the DataConverter was always False;
Then I put a breakpoing inside the DataConverter, to discover that, for example, the string "0.75" provided by ConverterParameter was being Double.Parsed as 75.0;
Then I realized that my current language is pt-BR, and the decimal separator is comma instead of dot. Then I changed the converter, adding an InvariantCulture parameter to Double.Parse. And now it works!
double limite = double.Parse((string)parameter, CultureInfo.InvariantCulture);
Your run time DataContext is not being set correctly, so your code can't bind to the correct properties at runtime.
Note that the run time DataContext is completely separate to the design time DataContext, and uses different XAML to setting the runtime DataContext.
I would recommend using Snoop to fix the problem, you can use it to flag up binding errors due to a bad runtime DataContext, see my answer here:
ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext"

Apply ValueConverter in style when source comes from x:Static resx file

After quite some search and reading other questions & posts, I was not able to find how to solve this. Note: I'm relatively new to WPF (not to binding in general).
Here's what I'm after:
I'd like to have all TextBlock controls in a window to be styled in some way.
The style should also apply a ValueConverter to make text all uppercase.
Finally, each TextBlock Text could come either from binding to a view-model property, or from binding to a resource string from a .resx file
Here's an excerpt of what I'm playing with:
<!-- in Window resources -->
<help:AllCapsStringConverter x:Key="AllCaps"/>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Brown" />
<Setter Property="Text">
<Setter.Value>
<Binding>
<Binding.Converter>
<help:AllCapsStringConverter />
</Binding.Converter>
<!-- also tried adding:
<Binding.RelativeSource>
<RelativeSource Mode="Self" />
</Binding.RelativeSource>
-->
</Binding>
<!-- also tried with:
<Binding Converter="{StaticResource AllCaps}"/>
<Binding Path="Text" Converter="{StaticResource AllCaps}"/>
-->
</Setter.Value>
</Setter>
</Style>
<!-- in Window content -->
<TextBlock Text="{x:Static resx:MyResources.MyTitle}" />
Here's the value converter, which on its own has proved to be working:
class AllCapsStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is string)
{
return ((string)value).ToUpper();
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
TextBlocks get the foreground color, but no conversion kicks in.
I was able to apply the converter locally to the single TextBlock, but I don't want to apply that to all TextBlocks around the window:
<TextBlock>
<TextBlock.Text>
<Binding Source="{x:Static resx:MyResources.MyTitle}"
Converter="{StaticResource AllCaps}"/>
</TextBlock.Text>
</TextBlock>
<!-- which is the same as -->
<TextBlock Style="{StaticResource CustomerInfoTitleStyle}"
Text="{Binding Source={x:Static resx:MyResources.MyTitle}, Converter={StaticResource AllCaps}}" />
Your converter is not working because your TextBlock is overriding the Text property of the Style, which includes the converter you have added to the binding.
For example:
<Grid.Resources>
<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Text" Value="You won't see this."></Setter>
</Style>
</Grid.Resources>
<TextBlock Text="You will see this." Style="{StaticResource MyTextBlockStyle}"/>
Hopefully from the above you can see why your approach is not working.
A better solution would just be to set the value of Text with the value converter on the TextBlock, rather than in the Style.
If you don't want to do that, one common cheat you could use to get around this could be to bind the TextBlock's Text property to the Tag property, like so:
<Grid.Resources>
<local:AllCapsConverter x:Key="AllCaps"/>
<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Text" Value="{Binding Tag, RelativeSource={RelativeSource Self}, Converter={StaticResource AllCaps}}"/>
</Style>
</Grid.Resources>
<TextBlock Tag="You will see this." Style="{StaticResource MyTextBlockStyle}"/>
I have a strong dislike of this approach, but it does get you what you want. I would prefer you just use the converter when you set the binding on the TextBlock.
lukegv's approach is another alternative. However, there is no need to use a Label, as you are overriding the template and (similar to my example above) you are just binding to the Content property of the Label. You could just as easily get what you need from a ContentControl.
<Grid.Resources>
<local:AllCapsConverter x:Key="AllCaps"/>
<Style x:Key="MyContentControl" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ContentControl Style="{StaticResource MyContentControl}" Content="You will see this too!"/>
I'm not a particularly huge fan of this idea either though, as you lose access to all the other TextBlock properties. For example, if I wanted to set the FontWeight property of my TextBlock then I would be stuffed.
Try to use a 'Label' and build a template, because a 'TextBlock' is not a control.
<Style x:Key="AllCapsLabel" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<TextBlock Foreground="Brown" Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And use it this way:
<Label Style="{StaticResource AllCapsLabel}" Content="whatever you want" />
If the content is plain text (aka a 'String'), the text should always be uppercase.

Style a WPF DataGrid row header when the selected cell resides in that row?

I have a WPF DataGrid that is configured to only allow a single cell selection, i.e.:-
SelectionMode="Single"
SelectionUnit="Cell"
What I'm trying to do is change the background of the row header of whichever row contains the currently selected cell. I've come up with the following so far, but it isn't working.
Here is the XAML style, which binds the background property to a multi-value converter. The converter is bound to the header's DataGridRow and the SelectedCells property of the DataGrid:-
<Style TargetType="{x:Type DataGridRowHeader}">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource dataGridHeaderBackgroundConverter}" Mode="OneWay">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}"
Path="SelectedCells"
Mode="OneWay"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
The multi-converter's Convert method looks like this (I've removed null checking code to keep it concise):-
var row = values[0] as DataGridRow;
var selectedCells = values[1] as IList<DataGridCellInfo>;
var selectedCell = selectedCells[0];
return selectedCell.Item == row.Item ? Colors.Red : Colors.LightGray;
The method only seems to get called when the DataGrid is initially rendered (when there are no selections). It doesn't get called after a cell has been selected, so what am I missing?
You can update your style as follows and write the EqualityConverter which would be a MultiValueConverter to be used to bind the DataGrid's CurrentCell and RowHeader context in the DataTrigger. So this trigger will be fired everytime you select the cell on your DataGrid.
<Style TargetType="{x:Type DataGridRowHeader}">
<Style.Triggers>
<DataTrigger Value="true">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityConverter}">
<Binding/>
<Binding Path="CurrentCell" RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
and in converter's Convert method:
if(values[0] == ((DataGridCellInfo)values[1]).Item)
{
return true;
}
return false;
Tested it.. worked well
Nitin's solution works perfectly, but during updating data in my data grid, this warning is raised:
System.Windows.Data Warning: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.DataGrid', AncestorLevel='1''. BindingExpression:Path=CurrentCell; DataItem=null; target element is 'DataGridRowHeader' (Name=''); target property is 'NoTarget' (type 'Object')
Any solution to get rid of this warning?
Anyway here is another working approach...
<DataGrid.RowHeaderStyle>
<Style TargetType="DataGridRowHeader">
<Setter Property="Background" Value="black" />
<Style.Triggers>
<Trigger Property="IsRowSelected" Value="True">
<Setter Property="Background" Value="white" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowHeaderStyle>

Dynamic Binding in WPF DataGridCell Template

I have a question about data binding DataGrid in WPF. I am using the VS 2010 Beta 2 which has its own DataGrid, not the Toolkit one, although I think it is pretty much the same.
I want to bind to a dataset which has 52 columns, one for every week of the year. For this reason I want to bind the data dynamically rather than specifying each field. The value for each field is true or false depending on some condition. Based on this value I want to show an image in the cell template if the condition is true and hide it if the condition is not true.
My problem is that all the examples of using templates that I have found refer to the case of fixed, predefined fields, where you can have a binding like Text ="{Binding UserName}". This is no good to me because I don't know what the field names will be at design time.
I have made up a simplified example which illustrates the problem. In this example a data table is generated which contains true and false values. The Image in my template is never visible. How would I make it invisible depending on the true or false value in the data?
<Window.Resources>
<!--This is the bit that doesn't work...-->
<Style TargetType="{x:Type Image}" x:Key="HideWhenFalse">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=???}"
Value="True"> <!--What to put for the path? -->
<Setter Property="Visibility">
<Setter.Value>
Visible
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<!--Up to here-->
<Style x:Key="{x:Type DataGridCell}" TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Image Source="Images/tick.bmp" Style="{StaticResource HideWhenFalse}">
</Image>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<DataGrid
x:Name="myDataGrid"
AutoGenerateColumns="True" >
</DataGrid>
</Grid>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataTable dtTable = new DataTable();
dtTable.Columns.Add("A", typeof(Boolean));
dtTable.Columns.Add("B", typeof(Boolean));
dtTable.Columns.Add("C", typeof(Boolean));
dtTable.Columns.Add("D", typeof(Boolean));
dtTable.Columns.Add("E", typeof(Boolean));
dtTable.Columns.Add("F", typeof(Boolean));
for (int i = 0; i < 5; i++)
{
object[] oValues = new Object[dtTable.Columns.Count];
for (int j = 0; j < dtTable.Columns.Count; j++)
{
oValues[j] = (j % 2 == 1) ? true : false;
}
dtTable.Rows.Add(oValues);
}
myDataGrid.ItemsSource = dtTable.DefaultView;
myDataGrid.Items.Refresh();
}
}
NB This is probably obvious and I am approaching the problem in completely the wrong way. Here is a confession: I have been trying to get my head around WPF for a couple of months now and I still seem to find myself approaching EVERY problem the wrong way. I hope the penny drops soon.
You can use a MultiBinding, with a first binding taking the actual data context from the cell (that would be the row), and the second one taking the column. From there, you can retrieve the cell value.
Converter code:
public class RowColumnToCellConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
DataRowView row = values[0] as DataRowView;
DataGridColumn column = values[1] as DataGridColumn;
return row != null && column != null
? row[column.SortMemberPath]
: DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotSupportedException();
}
}
XAML:
<Style x:Key="{x:Type DataGridCell}" TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<TextBlock x:Name="TextOK" Text="OK" Visibility="Collapsed" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource RowColumnToCellConverter}">
<Binding />
<Binding RelativeSource="{x:Static RelativeSource.Self}" Path="Column" />
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="TextOK" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I used a TextBlock instead an image for testing, but the code will be the same. Just avoid defining a style for the image if that can be done directly in the DataGridCell's style.

Resources