How to bind an automatically-set parameter in WPF with MVVM - c#

So here's the situation:
I have a label (custom control derviative of it, if it matters), and I need to get its width and height with MVVM. However, if I set either of the parameters to {Binding XXX}, they are no long Auto and thus when I change the font in runtime their size doesn't update.
I read about using ActualWidth/Height, which sounds like just what I need besides the fact that it's not a dependence parameter, thus it seemss like I'd need to break MVVM for it.
Are there any better solutions?
EDIT
Well, the element in XAML looks nothing special. Just a million of bindings.
<local:DraggableLabel
Content="123"
Margin="{Binding Label2Position, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontFamily="{Binding Label2Font.Family}"
FontSize="{Binding Label2Font.Size}"
FontWeight="{Binding Label2Font.Weight}"
FontStyle="{Binding Label2Font.Style}"
Foreground="{Binding Path=Label2Color,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource ColorToBrushConverter}
}"/>
The default is,
Width="Auto"
which doesn't have to be written explicits, but can (changes nothing). It makes it resize when the font changes. If I set it to
Width="Binding {Label1Width}"
The binding works fine, but I no longer get the auto-adjustment.

Okay, so I found a workaround.
Since I had create a custom control, I was able to create a new DependencyProperty called RealWidth, then I added a OnSizeChanged event that updates it with the value of ActualWidth that you CAN get from inside the element every time the size of the element changes.

Related

Why is my DataContext getting switched out from under me for a binding?

I'm trying to use the PropertyGrid component from PropertyTools to display information on an object. I can bind the object easily enough -- it's a property on my DataContext -- but one of the things that can't be derived from the object is the name that should be displayed in the tab header. (And I can't change that; the object I'm inspecting comes from a third party.) The proper name is a different property on my DataContext.
PropertyGrid has a way to change the way the tab header is displayed, by passing a DataTemplate to its TabHeaderTemplate property. But something bizarre happens inside of the template: my DataContext is gone, replaced by something else. When I try to say {Binding TabName} in the appropriate place inside the context, it errors out and tells me that TabName is not a valid property on class Tab. But my DataContext class isn't called Tab; that's something inside of PropertyTools's codebase!
I'm still new to WPF, so I have no clue what's going on here. Why is the in-scope DataContext that's perfectly valid in the rest of the XAML file being yoinked out from under me inside this template, and how can I fix it?
EDIT: Posting the XAML as requested. The template is literally just the simplest possible thing:
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplate">
<TextBlock Text="{Binding TabName}" />
</DataTemplate>
</UserControl.Resources>
And then further down the page,
<props:PropertyGrid
SelectedObject="{Binding Value}"
TabHeaderTemplate="{StaticResource HeaderTemplate}" />
But for some bizarre reason, in the template it's trying to interpret the binding inside the wrong DataContext!
In this case, just be sure to specify the source in your binding. There are a few ways to do this. One is to use the RelativeSource property of the Binding. Another is to use ElementName
Give your UserControl this attribute:
x:Name="Root".
Then change your binding to use it
<TextBlock Text="{Binding ElementName=Root, Path=DataContext.TabName}" />
Or use this:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}}, Path=DataContext.TabName}"/>

WPF - TextBox not binding properly when set to readonly

I have TextBox that I'm using to add (only to add and not read) file path into DB. Text property is set when user selects certain file (OpenFileDialog). So, I set it in readonly state and it won't bind properly. When I remove readonly it works fine.
<Button Name="btnAddFile" Content="+" HorizontalAlignment="Left" Width="23" Height="23" Click="AddFilePath"/>
<TextBox Name="tbxFilePath" Height="23" Text="{Binding FilePath}" Width="364" IsReadOnly="True"/>
When I use:
Text="{Binding FilePath, Mode=OneWayToSource}"
it sometimes work but most of the time it doesn't (?!). I could use TextBlock or Label but I would really like to understand what is going on and use TextBox.
I'm using Entity Framework but don't think it does matter.
Question: How can I programmatically add text to TextBox control which is readonly and be able to bind it.
EDIT: I figured out what the problem is. When I set focus on TextBox after I set it's Text property from code-behind, it works. I guess it has to notify that Text is changed when I do it from code-behind. How to do that?
Have you tried using OneWay Binding?
MSDN reads:
OneWay Updates the binding target (target) property when the binding source (source) changes. This type of binding is appropriate if the control being bound is implicitly read-only.
Which I think covers your scenario.
The target is your TextBox Text property and your source is your FilePath property on your ViewModel.
Use:
Text="{Binding FilePath, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
EDIT
This answer assumes you have implemented INotifyPropertyChanged on your ViewModel.
EDIT
The correct binding mode is OneWayToSource. Confirmed by OP.

WPF DataGrid - Binding Source and Width to different objects

In my WPF application, I have a DataGrid which is bound to a collection in the viewmodel, but I want the width of the first column to be equal to a property on the viewmodel itself, like so:
public ObservableCollection<Booking> Bookings
{
return repository.Bookings();
}
public int MaxWidth
{
return 100;
}
(I realise there's no point in binding to a fixed property like this - it's for demo purposes)
<UserControl>
<DataGrid DataContext="{Binding Bookings}">
<DataGrid.Columns>
<DataGridTextColumn Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.MaxWidth}" Header="Provider" Binding="{Binding Name}" />
</DataGrid.Columns>
</DataGrid>
</UserControl>
But if I do this, the width of the column remains at the default value - i.e. just wide enough to accommodate its value.
What am I doing wrong?
EDIT: Attempted this, to see what happened:
<Label Name="tricky" Content="500" Visibility="Collapsed"></Label>
...
<DataGridTextColumn Width="{Binding ElementName=tricky, Path=Content}" Header="Provider" Binding="{Binding Name}" />
Which did nothing. Is it not possible to set the width dynamically?
I think the problem is the DataGridTextColum is not part of the Visual Tree (can verify with Snoop), so the binding never gets resolved.
The data obtained from the DataGridTextColumn is used to build the DataGridCell template, and at the time the template is built, the DataContext isn't set.
My suggestion would be to use a DataGridTemplateColumn, and specify your own CellTemplate that has the Width binding you need.
Alternatively a useful feature I use is as follow
IsEnabled="{Binding DataContext.IsEnabled,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
This allows you to link to properties outside of the itemssource of your grid.
MaxWidth and Bookings are siblings aren't they? So binding expressions for them should be identical.
Looking at your first XAML: Path=DataContext.MaxWidth - remove DataContext.
Looking at the second XAML: no need to specify Path twice, the first Path is enough, I'm not even sure if the actual construct is valid.
Generally, a lot of low level UI properties are POCOs rather than DPs, just make sure your target one is a DP.
Update - what you might want to do is to use FallbackValue = 900 as part of your binding expression, just to narrow the problem down - whether it's a binding problem or the target property isn't suitable for using as binding target.
Update 2 - most times you'll find yourself running out of steam rather quickly when going for that low level of managing your UI, it's often beneficial to create MVColum, managing headers, widths etc and create a behavior, which can apply all these settings in one go. Also that way you won't be dependent on a flavor of your target properties as you'll be setting them up right in your code.

how do i force the converter to execute when the property is changed?

This is my image in XAML:
<Image Margin="0"
Stretch="UniformToFill"
Source="{Binding '', Converter={StaticResource byteArrToBitmap}}">
<ToolTipService.ToolTip>
<Border BorderBrush="#FF3D3D3D" Background="#FFFFE1E1">
<TextBlock Text="{Binding PhotoDescription, TargetNullValue=No description}"
Width="170"
Height="Auto"
FontFamily="Georgia"
TextWrapping="Wrap"
Foreground="#FF373737"/>
</Border>
</ToolTipService.ToolTip>
</Image>
This Image is inside DataTemplate of listbox. As you can see I have source set to {Binding ''} which means it is bound to datacontext and not to the actual property that I want to bind. This is essential because I have some logic being performed based on which I am returning an image.
I am downloading the images on the fly from webservices and it returns a byte[]. I have INotifyPropertyChanged implemented in the class. However, as I have binding setup to the DataContext, the converter does not reexecute itself when the byte[] is downloaded in asynchronous manner.
It's a verty bad idia to bind something to the DataContext it self.. right now Silverligth 4 do not implement INotifyPropertyChanged for DataContext, so you have two options:
1) wait for Silverligt 5:
Silverlight 5–Features list
The DataContextChanged event is being introduced. Markup extensions allow code to be run at XAML parse time for both properties and event handlers, enabling cutting-edge MVVM support.
2) create some object that implement INotifyPropertyChanged, create some property, and bind to that property...
I believe you just want
{Binding Converter={StaticResource byteArrToBitmap}}
Not
{Binding '', Converter={StaticResource byteArrToBitmap}}
As not specifying any property path will bind to the DataContext. I have no idea what {Binding ''} does, but it's not standard practice. I'm surprised it doesn't throw an exception, actually.
That said, the way I would handle this is to have a wrapper object, which has a property representing the byte array - that way you can raise INotifyPropertyChanged events in a more straightforward way. I believe there's a way to invalidate the whole object, but I don't recall what it is.
I am assuming that your data context is the byte[] that you want to convert. So you have to ensure that the PropertyChanged event is raised whenever the asynchronous download is complete. Also, ensure that the event is raised on the Main Thread and not on a Worker or Background thread.
If you change your binding to be bound to some property, even if its unnecessary in the scope of your application you can indirectly cause the Converter to reevaluate when that property changes. That property needs to exist on an object that implements INotifyPropertyChanged.
I can give source code if needed.

Tabbing or Deselecting Cell not Committing Data

I am working with the the WPF Toolkit DataGrid and currently have a problem with committing data back to the source.
My grid is bound to a CLR object list and I have a converter with both the convert and convert back methods implemented.
The two way binding works fine if the user hits Enter in the cells but if they deselect or tab out of the cells the data that was typed is lost.
I have put a break on the CellEditEnding event and both events for Tab and Enter seem identical, but when it gets to the ConvertBack method on my converter the value is empty.
Any help would be much appreciated.
Try changing the UpdateSourceTrigger parameter of your control's Binding to PropertyChanged instead of the default LostFocus.
Eg
<TextBox
Width="75"
VerticalAlignment="Top"
Margin="10"
Text="{Binding
Source={StaticResource data},
Path=Age,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True}"
Style="{StaticResource textBoxInError}" />

Categories

Resources