0

I need to bind the height of TextBoxes to my data model. The model should be updated each time a TextBox gets resized. The TextBox is resized because it wraps the content until MaxHeight is reached, when it's reached it shows the scrollbar. I've made a little example to demonstrate my problem.

<Window x:Class="BindingTester.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:BindingTester"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Canvas>
    <TextBox 
        Canvas.Left="234" 
        Canvas.Top="71"
        Text="TextBox"
        TextWrapping="Wrap"
        ScrollViewer.VerticalScrollBarVisibility="Auto" 
        MaxHeight="200"
        AcceptsReturn="True"
        Height="{Binding TextBoxHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged/>
</Canvas>

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private double textBoxHeight;

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        //TextBoxHeight = 100.0;
    }

    public double TextBoxHeight
    {
        get { return textBoxHeight; }
        set
        {
            if (value != textBoxHeight)
            {
                textBoxHeight = value;
                RaisePropertyChanged("TextBoxHeight");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

When I use this, the binding from source to target works fine. When I set the TextBoxHeight property in the constructor of the MainWindow the TextBox resizes perfectly to 100, but the size seems to be fixed then. When I don't set it (because with a height of 100 it's much to large for the content "TextBox") first behaves like expected: the TextBox's size fits the content. The TextBoxHeight in the model gets updated to NaN.

I know this happens because the Height property returns NaN when it is not set, instead I have to ask for ActualHeight. But anyway, I recognized that if I enter some text (for example newlines) to resize the TextBox's Height, the TextBoxHeight property still is not updated and the setter is not called again…I also have tried to use an IValueConverter to update it using ActualHeight without success.

I know in this example, even if I resize the TextBox by entering newlines, TextBoxHeight each time would be updated with NaN, but the setter is just called the first time when the TextBox is initialized. It confuses me that the Binding doesn't seem to work…I know a solution for the problem itself: subscribe the SizeChanged event, get the DataContext of sender object and set the model manually. But I think there should be a solution without subscribing and accessing the model in code-behind, only binding the properties. Can anybody help?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
Dennis
  • 23
  • 1
  • 9
  • Since `ActualHeight` is the property reflecting the `TextBox`'s actual height, why aren't you binding to that? It's not really clear to me what you're trying to accomplish here. I will point out that, at least in the code example above, you have a syntax error in the XAML, as you're missing the closing `}"` in the `Height` attribute value. Do you have that same problem in your actual code? – Peter Duniho Nov 08 '15 at 02:23
  • 1
    Hi Peter. First thanks for your answer! the syntax error is just the result of copy paste ;) As fahr as I know the ActualHeight property is not settable, it only has a getter. Maybe I'm misunterstanding you but that means to me that I'm not able to bind like this: ActualHeight={Binding ...} – Dennis Nov 08 '15 at 13:31
  • @PeterDuniho you can not do binding to ActualHeight. It has Getter only, It can only return the calculated value https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.actualheight(v=vs.110).aspx, the suggested way is a behavior I described before or Trigger to Command way. – Ilan Nov 08 '15 at 20:18
  • @PeterDuniho you can not access that in xaml, the actual is known at the moment when the control is actually after the step it was measured and arranged. It is calculated value. – Ilan Nov 08 '15 at 20:25
  • @Ilan: it is correct that you can't bind read-only properties in XAML. But there is already ample discussion Stack Overflow addressing this concern. E.g. [Pushing read-only GUI properties back into ViewModel](http://stackoverflow.com/q/1083224). I'm trying to figure out what the OP is asking that is not already answered elsewhere. – Peter Duniho Nov 08 '15 at 20:29
  • @PeterDuniho WHAT IS A REAL DIFFERENT BETWEEN THE http://stackoverflow.com/questions/1083224/pushing-read-only-gui-properties-back-into-viewmodel, WHICH IS A SET OF ATTACHED PROPERTIES AND THE WORKING BEHAVIOR BASED SOLUTION I'VE SUGGESTED? WHAT IS A REAL DIFFERENT ;)? – Ilan Nov 08 '15 at 20:39
  • @Ilan: The attached properties in the referenced question are there to provide a _generalized_ solution. The basic approach is simply to use `SizeChanged` as the OP has said they have already done successfully. If that really is all they are asking, then this question is a duplicate of the other. If not, they need to clarify the question. – Peter Duniho Nov 08 '15 at 20:55

2 Answers2

0

After reading your question multiple times, I have drawn following conclusion : You want current height of textbox in your TextBoxHeight variable. You want TextBox height to change if you add more text, but scroll-bars should be visible only if TextBox height crosses 100.

Following code will increase height of TextBox upon adding more text. If you reduce text , height of TextBox will decrease too upto MinHeight value.

    <TextBox x:Name="Tbx1"
            Width="219"          
            TextWrapping="Wrap"
            ScrollViewer.VerticalScrollBarVisibility="Auto" 
            MaxHeight="100"
            AcceptsReturn="True"
            MinHeight="{Binding TextBoxHeight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            SizeChanged="Tbx1_SizeChanged"
     />

////////

private void Tbx1_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            TextBoxHeight = Tbx1.ActualHeight;
        }

If I have not understood your requirement correctly, correct me please.

AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38
  • Thank for your answer. You understood a part of my problem correct, but not everything...maybe I was not clear enough: I want a cleaner solution, which supports MVVM pattern and no code in codebehind. Additionally I should mention that in the real project the TextBox(es) are embedded in a Datatemplate binding to a collection in the VM., Maybe I'm missing something, but giving it a Name would mean to name multiple elements with the same name, and that's not possible, is it? – Dennis Nov 09 '15 at 20:38
  • You can use sender parameter in even handler to get the TextBox. I demonstrated the solution. Here people will give you hints as it is a Q&A site and not some Freelancer site. Core hint is needed. Rest can be done easily. I will post now using your MVVM and DataTemplate scenario. – AnjumSKhan Nov 10 '15 at 03:59
-1

I can suggest you to use behavior or some another event to command mechanism and then you can listen to the OnResizeAction event to handle the size changes and convert these event to any logic you want. Here is a small example. 1. Xaml code:

<Window x:Class="SoResizeIssue.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:soResizeIssue="clr-namespace:SoResizeIssue"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <soResizeIssue:MainViewModel/>
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <TextBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
             Text="{Binding ContentFromDataContext, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
        <i:Interaction.Behaviors>
            <soResizeIssue:ResizeHandlingBehavior OnResizeAction="{Binding OnResizeAction, UpdateSourceTrigger=PropertyChanged}"/>
        </i:Interaction.Behaviors>
    </TextBox>
</Grid></Window>

2. Behavior code:

public class ResizeHandlingBehavior:Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SizeChanged += AssociatedObjectOnSizeChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.SizeChanged -= AssociatedObjectOnSizeChanged;
    }

    private void AssociatedObjectOnSizeChanged(object sender, SizeChangedEventArgs args)
    {
        var action = OnResizeAction;
        if(action == null) return;
        action(args);
    }

    public static readonly DependencyProperty OnResizeActionProperty = DependencyProperty.Register(
        "OnResizeAction", typeof (Action<object>), typeof (ResizeHandlingBehavior), new PropertyMetadata(default(Action<object>)));

    public Action<object> OnResizeAction
    {
        get { return (Action<object>) GetValue(OnResizeActionProperty); }
        set { SetValue(OnResizeActionProperty, value); }
    }
}

3. ViewModel code:

public class MainViewModel:BaseObservableObject
{
    private string _contentFromDataContext;
    private Action<object> _onResizeAction;

    public MainViewModel()
    {
        OnResizeAction = new Action<object>(InnerOnResizeAction);
    }

    private void InnerOnResizeAction(object obj)
    {
        var args = obj as SizeChangedEventArgs;
        //do you logic here
    }

    public string ContentFromDataContext
    {
        get { return _contentFromDataContext; }
        set
        {
            _contentFromDataContext = value;
            OnPropertyChanged();
        }
    }

    public Action<object> OnResizeAction
    {
        get { return _onResizeAction; }
        set
        {
            _onResizeAction = value;
            OnPropertyChanged();
        }
    }
} 

Updates 5. This solution moves the logic of Resize to the ViewModel side, while the TextBlock will change his size due to the Layout re-build and actual size changes. Using this solution you don't do the binding to some actual size parameters you just observe the size changes and move all the logic from window code behind to the view model side, thus the window's (xaml.cs) code behind stays completely clear.

  1. There are another way to redirect an event trigger to view model named Trigger to Command pattern, here are the links: Binding WPF Events to MVVM ViewModel Commands, How to trigger ViewModel command for a specific button events and Firing a Command within EventTrigger of a style?.

I hope it will help you. Thanks and regards,

Community
  • 1
  • 1
Ilan
  • 2,762
  • 1
  • 13
  • 24
  • Hi Ilan, this seems to be the solution I was searching for! :) Never heard about Behaviour before, but that looks quite cool. Am I right in my assumption that I can use this for Drag&Drop the Textbox (or a Border surrounding it) within my root Canvas (the Canvas is neccessary), too? – Dennis Nov 08 '15 at 13:36
  • @Dennis yes, if you want to handle the TextBox resize event. – Ilan Nov 08 '15 at 13:55
  • Please explain how this is different from/superior to subscribing to the `SizeChanged` event. The OP already knew the latter would work; what does going through a `Behavior` do that wasn't possible with `SizeChanged`? Why is this proposal better than what they already had? – Peter Duniho Nov 08 '15 at 18:46
  • @PeterDuniho the work with behavior is better than just to subscribe to the event you talking about, and handle its callback in code-behind, first of all because of the fact that a behavior can support mvvm in better way. there is another mechanism supporting mvvm to do that. I've said about that in my answer. second the thing they already had doesn't work, and never won't work the way they want. – Ilan Nov 08 '15 at 19:47
  • You didn't say anything in your answer about how behaviors "can support mvvm in better way", nor has your comment done anything to provide any understanding of that. The OP has stated specifically that using `SizeChanged` _does_ work for them, so obviously you are mistaken that it "doesn't work" (I would agree that it "never won't work", except that you probably didn't really mean to write that in the first place, as it contradicts everything else you wrote) – Peter Duniho Nov 08 '15 at 19:58
  • @PeterDuniho they wanted to find a better way than code nehind. behaviors can support mvvm in better way - http://www.jayway.com/2013/03/20/behaviors-in-wpf-introduction/, and http://stackoverflow.com/questions/14492694/how-do-behaviors-and-viewmodels-relate-in-mvvm and more. – Ilan Nov 08 '15 at 20:11
  • @PeterDuniho THE SOLUTION I PUBLISHED IS A WORKING SOLUTION, JUST COPY/PAST IT AND CHECK IT OUT. THIS IS A GOOD MVVM BASED SOLUTION OF THEIR PROBLEM. PUBLISH YOUR SOLUTION AND WILL SEE, JUST TRY TO UNDERSTAND THE QUESTION THE OP HAVE. REGARDS ;)!!! – Ilan Nov 08 '15 at 20:34
  • A good answer _explains_ itself. All you've done here is a code-dump. And frankly, "yelling" doesn't help anyone understand you better, no matter how many emoticons you add. If in fact you have actually addressed the OP's question (which IMHO is not even clear), then his question is actually a duplicate of the other one I referenced (which uses `SizeChanged`). – Peter Duniho Nov 08 '15 at 20:54
  • @PeterDuniho dear Peter the OP alredy said that it is a seems like a solution they are searching for. The solution is good mvvm solution to move the logic of REACTING on resize event to the ViewModel side and moreovere this provide the OP with a new key to solve problems (behavior). But never mind, thank you very match for your comments... – Ilan Nov 09 '15 at 06:27
  • @Dennis edited with som explanations and additional resolving suggestions. regards. – Ilan Nov 09 '15 at 06:51
  • @Peter thanks for the link to the other question. that are good answers too, but it seems to me (this is an assumption after doing some more research) that Behaviours are the suggested solution by Microsoft for my issue (and of course similar problems). – Dennis Nov 09 '15 at 21:05
  • @Ilan Thanks for the additional Links! Triggers look like a possibility. I think the advantage of Behaviours is that I'm able to do an better encapsulation of View and ViewModel logic if the code gets more complex. So I think I'll stick to it! :) – Dennis Nov 09 '15 at 21:06