none
Getting UI property in viewmodel

    Question

  • Here's my scenario:

    1. I have a ListBox whose Width is set by its container.
    2. I have a a viewmodel that binds to the ListBox's parent. The vm has a collection which is the ListBox's ItemsSource.
    3. In the viewmodel, I need to know what the width of the ListBox is. Can this be done using some sort of two way binding? I can't assign the width - it has to be got at runtime from whatever layout is there.

    Thanks. 

    Tuesday, January 17, 2012 8:40 AM

Answers

  • Add a width property to your viewmodel and bind the ListBox ActualWidth, OneWay.  See if that works for you.

    ... time passes ...

    Oops, ActualWidth isn't a dependency property so can't be bound.  But you CAN get the width with ActualWidth.

    Tuesday, January 17, 2012 8:51 AM
  • IF you want to CONTROL the width of the box, you can bind to Width.  If you want the box to figure out its own width (which I would recommend as it gives the most flexibility in design), then use ActualWidth for the current width of the Listbox.

    If your Viewmodel SETS the width, then oneway binding is okay since I don't think the ListBox will change its width if something has already set it.  Therefore, you KNOW the width and OneWay is sufficient.

    Tuesday, January 17, 2012 9:03 AM
  • As I said, you can GET the actual width with the ActualWidth property, but no, there doesn't appear to be something that gives the width through binding.

    Tuesday, January 17, 2012 9:11 AM
  • This page:

    http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.actualwidth%28v=vs.95%29.aspx

    found by googling says:

    For purposes of ElementName binding, ActualWidth does not post updates 
    when it changes (due to its asynchronous and run-time calculated nature).
    Do not attempt to use ActualWidth as a binding source for an ElementName
    binding. If you have a scenario that requires updates based on ActualWidth,
    use a SizeChanged handler.
    Tuesday, January 17, 2012 9:14 AM
  • I was just giving Interactivity a thought.  I think you could trigger a command when SizeChanged and then have the command change your viewmodel.  That wouldn't be too bad.

    Tuesday, January 17, 2012 9:17 AM
  • Here is an example where I triggered a command in the viewmodel on the SelectionChanged event of a DataGrid.  If you haven't done Interactivity before, you can model after this:

                <sdk:DataGrid 
                   Name="dataGrid1" 
                   Grid.Column="1"
                   Grid.Row="1"
                   ItemsSource="{Binding UnobservedPeople}"
                   AutoGenerateColumns="False" 
                   VerticalScrollBarVisibility="Visible" 
                   VerticalAlignment="Top"
                   SelectionMode="Extended" 
                   RowBackground="White" 
                   Width="200"
                   MinHeight="75" 
                   >
                   <sdk:DataGrid.Columns>
                      <sdk:DataGridTextColumn 
                         Header="People Unobserved"
                         Binding="{Binding LastnameFirstname}"
                         MinWidth="181"
                         />
                   </sdk:DataGrid.Columns>
                   <i:Interaction.Triggers>
                      <i:EventTrigger EventName="SelectionChanged">
                         <i:InvokeCommandAction
                            Command="{Binding SelectedUnobservedChangedCommand}"
                            CommandParameter="{Binding SelectedItems, ElementName=dataGrid1}" 
                            />
                      </i:EventTrigger>
                   </i:Interaction.Triggers>
                </sdk:DataGrid>
    

    And of course you would need the namespace:

       xmlns:i       ="http://schemas.microsoft.com/expression/2010/interactivity"
    

    And the reference to:

    System.Windows.Interactivity


    In this case, I was passing the SelectedItems as a parameter, but you wouldn't have to do any parameter passing.

    Tuesday, January 17, 2012 9:20 AM
  • I said, I don't think you need any parameter.  And you certainly wouldn't want Width.  Probably Width isn't set to anything (so that it can determine its ActualWidth on its own).

    However, I think what you DO want to have as the command parameter is ActualWidth.  And if that isn't allowed, then I would just have the parameter be the MainList object itself.  Then with that in hand, you could definitely get to the ActualWidth.

    I don't use all that RelayCommand stuff, so I don't know what it is trying to tell you.

    Maybe post the full, exact message.

    Tuesday, January 17, 2012 10:33 AM
  • Okay, although this is in Delphi Prism and doesn't use the RelayCommand stuff, I hope it will help you work it out.

    It DOES work.  When the listbox changes width, the Viewmodel property is changed.

    So here is the xaml:

    <?xml version='1.0' encoding='utf-8' ?>
    <UserControl 
       
       x:Class="SilverlightApplication85.MainPage"
       
       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"
       mc:Ignorable="d"
       d:DesignHeight="300" d:DesignWidth="400"
       
       xmlns:sys="clr-namespace:System;assembly=mscorlib"
       xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
       
       xmlns:local="clr-namespace:SilverlightApplication85"
       >
       <UserControl.Resources>
          <local:Viewmodel x:Key="vm" />
       </UserControl.Resources>
    
       <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource vm}">
          
          <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto"/>
             <ColumnDefinition Width="Auto"/>
          </Grid.ColumnDefinitions>
    
          <ListBox  ItemsSource="{Binding MyChoices}"  x:Name="listbox1" HorizontalAlignment="Left">
             <i:Interaction.Triggers>
                <i:EventTrigger EventName="SizeChanged">
                   <i:InvokeCommandAction 
                      Command="{Binding SizeChangedCommand}"
                      CommandParameter="{Binding ElementName=listbox1}"
                      />
                </i:EventTrigger>
             </i:Interaction.Triggers>
          </ListBox>
          <Button Content="test" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click" Grid.Column="1"/>
    
       </Grid>
    </UserControl>
    

    Note that I like to create the Viewmodel on the Page as a static resource.  Then the layoutRoot DataContext is set to that.  So everything is hooked up at that point by the xaml.

    Here is the code:

    namespace SilverlightApplication85;
    
    interface
    
       uses
          System,
          System.Windows,
          System.Windows.Input,
          System.Collections.Generic;
    
       type
          Viewmodel = public class
             public
                constructor;
    
                property SizeChangedCommand  : SizeChangedCommand; notify;
                property MyChoices           : List<String>;       notify;
                property ListBoxActualWidth  : Double;             notify;
             end;
    
          SizeChangedCommand = public class( ICommand )
             public
                method   Execute(    parameter: System.Object );
                method   CanExecute( parameter: System.Object ) : System.Boolean;
    
                event    CanExecuteChanged : System.EventHandler;
    
                property MyViewmodel : Viewmodel;
             end;
    
          MainPage = public partial class(System.Windows.Controls.UserControl)
             private
                method Button_Click(sender: System.Object; e: System.Windows.RoutedEventArgs);
             public
                constructor;
                end;
      
    implementation
    
       uses
          System.Windows.Controls;
    
       constructor MainPage; 
          begin
          InitializeComponent();
          end;
    
       method MainPage.Button_Click(sender: System.Object; e: System.Windows.RoutedEventArgs);
          begin
          MessageBox.Show( Viewmodel( LayoutRoot.DataContext ).ListBoxActualWidth.ToString );
          end;
    
    
       method SizeChangedCommand.CanExecute(parameter: System.Object): System.Boolean;
          begin
          result := true;
          end;
    
       method SizeChangedCommand.Execute(parameter: System.Object);
          begin
          MyViewmodel.ListBoxActualWidth := ListBox( parameter ).ActualWidth;
          end;
    
       constructor Viewmodel;
          begin
          MyChoices := new List<String>;
          MyChoices.Add( '1' );
          MyChoices.Add( '2' );
          MyChoices.Add( '3' );
    
          SizeChangedCommand := new SizeChangedCommand;
          SizeChangedCommand.MyViewmodel := self;
          end;
    
    end.

    When the Viewmodel is constructed, it just creates the Command and sets the command's MyViewmodel property so that the command can get a reference to which instance to update when the command is triggered.

    When the command is executed, it just sets its viewmodel ListboxActualWidth property to the new ActualWidth.

    When the button is pressed, it gets addressability to the viewmodel through the layoutRoot's DataContext and then shows the ListboxActualWidth.

    The Delphi Prism code splits the interface and implementation into separate sections (which I LOVE because you can get a sense of what the object IS before having to look at any real code).

    Also Delphi Prism has a "notify" directive.  That takes care of all the INotifyPropertyChanged stuff without any more "fluff" in the code.

    Hope this helps.

    Tuesday, January 17, 2012 11:27 AM

All replies

  • Add a width property to your viewmodel and bind the ListBox ActualWidth, OneWay.  See if that works for you.

    ... time passes ...

    Oops, ActualWidth isn't a dependency property so can't be bound.  But you CAN get the width with ActualWidth.

    Tuesday, January 17, 2012 8:51 AM
  • No...that doesn't work. It expects a writeable property which ActualProperty is not. 

    OneWay would probably not work since it would take the value from the vm and update the UI...I don't want to set the value in the vm - I just want to know what the width of the listbox is at runtime (from within the viewmodel).

    Tuesday, January 17, 2012 9:00 AM
  • IF you want to CONTROL the width of the box, you can bind to Width.  If you want the box to figure out its own width (which I would recommend as it gives the most flexibility in design), then use ActualWidth for the current width of the Listbox.

    If your Viewmodel SETS the width, then oneway binding is okay since I don't think the ListBox will change its width if something has already set it.  Therefore, you KNOW the width and OneWay is sufficient.

    Tuesday, January 17, 2012 9:03 AM
  • I need to know the ActualWidth from within the viewmodel. In WPF, this probably could be achieved by binding ActualWidth with OneWayToSource. Another operation within the viewmodel needs to know what the actual width of the listbox is. Is there any way of getting to that?

    Tuesday, January 17, 2012 9:07 AM
  • As I said, you can GET the actual width with the ActualWidth property, but no, there doesn't appear to be something that gives the width through binding.

    Tuesday, January 17, 2012 9:11 AM
  • This page:

    http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.actualwidth%28v=vs.95%29.aspx

    found by googling says:

    For purposes of ElementName binding, ActualWidth does not post updates 
    when it changes (due to its asynchronous and run-time calculated nature).
    Do not attempt to use ActualWidth as a binding source for an ElementName
    binding. If you have a scenario that requires updates based on ActualWidth,
    use a SizeChanged handler.
    Tuesday, January 17, 2012 9:14 AM
  • Yup...I was just looking into doing it with SizeChanged as well. What would be the easiest way to have a viewmodel method called when SizeChanged fires?

    Tuesday, January 17, 2012 9:16 AM
  • I was just giving Interactivity a thought.  I think you could trigger a command when SizeChanged and then have the command change your viewmodel.  That wouldn't be too bad.

    Tuesday, January 17, 2012 9:17 AM
  • Here is an example where I triggered a command in the viewmodel on the SelectionChanged event of a DataGrid.  If you haven't done Interactivity before, you can model after this:

                <sdk:DataGrid 
                   Name="dataGrid1" 
                   Grid.Column="1"
                   Grid.Row="1"
                   ItemsSource="{Binding UnobservedPeople}"
                   AutoGenerateColumns="False" 
                   VerticalScrollBarVisibility="Visible" 
                   VerticalAlignment="Top"
                   SelectionMode="Extended" 
                   RowBackground="White" 
                   Width="200"
                   MinHeight="75" 
                   >
                   <sdk:DataGrid.Columns>
                      <sdk:DataGridTextColumn 
                         Header="People Unobserved"
                         Binding="{Binding LastnameFirstname}"
                         MinWidth="181"
                         />
                   </sdk:DataGrid.Columns>
                   <i:Interaction.Triggers>
                      <i:EventTrigger EventName="SelectionChanged">
                         <i:InvokeCommandAction
                            Command="{Binding SelectedUnobservedChangedCommand}"
                            CommandParameter="{Binding SelectedItems, ElementName=dataGrid1}" 
                            />
                      </i:EventTrigger>
                   </i:Interaction.Triggers>
                </sdk:DataGrid>
    

    And of course you would need the namespace:

       xmlns:i       ="http://schemas.microsoft.com/expression/2010/interactivity"
    

    And the reference to:

    System.Windows.Interactivity


    In this case, I was passing the SelectedItems as a parameter, but you wouldn't have to do any parameter passing.

    Tuesday, January 17, 2012 9:20 AM
  • Thanks so much for your help so far. This is what I have now:

    <i:Interaction.Triggers>
     <i:EventTrigger EventName="SizeChanged">
            <i:InvokeCommandAction
                CommandName="SizeChangedCommand"
                CommandParameter="{Binding Path=Width, ElementName=MainList}"
                />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    

    That's in the ListBox (named MainList). VM wise, the following are there:
    public RelayCommand SizeChangedCommand { getset; }
    
    public void SizeChanged(object o)
    {
        var f = o;
    }
    
    
    
    And I hook up the command in the ctor with:
    SizeChangedCommand = new RelayCommand(SizeChanged, x=>true);
    
    
    
    However, at this point, it's simply blowing over saying something like the InvokeActionCommand 
    needs to be in a behaviour or something.
    Any ideas?

    Tuesday, January 17, 2012 10:12 AM
  • I said, I don't think you need any parameter.  And you certainly wouldn't want Width.  Probably Width isn't set to anything (so that it can determine its ActualWidth on its own).

    However, I think what you DO want to have as the command parameter is ActualWidth.  And if that isn't allowed, then I would just have the parameter be the MainList object itself.  Then with that in hand, you could definitely get to the ActualWidth.

    I don't use all that RelayCommand stuff, so I don't know what it is trying to tell you.

    Maybe post the full, exact message.

    Tuesday, January 17, 2012 10:33 AM
  • I'll try a few more things and then post the code on github or something. (I've tried setting CommandParameter={Binding Path=Width, Elementname=MainList} as well...doesn't seem to work either.

    Tuesday, January 17, 2012 10:39 AM
  • I'll give you a working example in about 10 minutes...

    ... working ...

    Having a little problem with CommandParameter for some reason.

    ... back shortly ...

    Tuesday, January 17, 2012 10:54 AM
  • Okay, I got it.  Let me neaten up a little and then I'll post.

    Tuesday, January 17, 2012 11:15 AM
  • Okay, although this is in Delphi Prism and doesn't use the RelayCommand stuff, I hope it will help you work it out.

    It DOES work.  When the listbox changes width, the Viewmodel property is changed.

    So here is the xaml:

    <?xml version='1.0' encoding='utf-8' ?>
    <UserControl 
       
       x:Class="SilverlightApplication85.MainPage"
       
       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"
       mc:Ignorable="d"
       d:DesignHeight="300" d:DesignWidth="400"
       
       xmlns:sys="clr-namespace:System;assembly=mscorlib"
       xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
       
       xmlns:local="clr-namespace:SilverlightApplication85"
       >
       <UserControl.Resources>
          <local:Viewmodel x:Key="vm" />
       </UserControl.Resources>
    
       <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource vm}">
          
          <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto"/>
             <ColumnDefinition Width="Auto"/>
          </Grid.ColumnDefinitions>
    
          <ListBox  ItemsSource="{Binding MyChoices}"  x:Name="listbox1" HorizontalAlignment="Left">
             <i:Interaction.Triggers>
                <i:EventTrigger EventName="SizeChanged">
                   <i:InvokeCommandAction 
                      Command="{Binding SizeChangedCommand}"
                      CommandParameter="{Binding ElementName=listbox1}"
                      />
                </i:EventTrigger>
             </i:Interaction.Triggers>
          </ListBox>
          <Button Content="test" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click" Grid.Column="1"/>
    
       </Grid>
    </UserControl>
    

    Note that I like to create the Viewmodel on the Page as a static resource.  Then the layoutRoot DataContext is set to that.  So everything is hooked up at that point by the xaml.

    Here is the code:

    namespace SilverlightApplication85;
    
    interface
    
       uses
          System,
          System.Windows,
          System.Windows.Input,
          System.Collections.Generic;
    
       type
          Viewmodel = public class
             public
                constructor;
    
                property SizeChangedCommand  : SizeChangedCommand; notify;
                property MyChoices           : List<String>;       notify;
                property ListBoxActualWidth  : Double;             notify;
             end;
    
          SizeChangedCommand = public class( ICommand )
             public
                method   Execute(    parameter: System.Object );
                method   CanExecute( parameter: System.Object ) : System.Boolean;
    
                event    CanExecuteChanged : System.EventHandler;
    
                property MyViewmodel : Viewmodel;
             end;
    
          MainPage = public partial class(System.Windows.Controls.UserControl)
             private
                method Button_Click(sender: System.Object; e: System.Windows.RoutedEventArgs);
             public
                constructor;
                end;
      
    implementation
    
       uses
          System.Windows.Controls;
    
       constructor MainPage; 
          begin
          InitializeComponent();
          end;
    
       method MainPage.Button_Click(sender: System.Object; e: System.Windows.RoutedEventArgs);
          begin
          MessageBox.Show( Viewmodel( LayoutRoot.DataContext ).ListBoxActualWidth.ToString );
          end;
    
    
       method SizeChangedCommand.CanExecute(parameter: System.Object): System.Boolean;
          begin
          result := true;
          end;
    
       method SizeChangedCommand.Execute(parameter: System.Object);
          begin
          MyViewmodel.ListBoxActualWidth := ListBox( parameter ).ActualWidth;
          end;
    
       constructor Viewmodel;
          begin
          MyChoices := new List<String>;
          MyChoices.Add( '1' );
          MyChoices.Add( '2' );
          MyChoices.Add( '3' );
    
          SizeChangedCommand := new SizeChangedCommand;
          SizeChangedCommand.MyViewmodel := self;
          end;
    
    end.

    When the Viewmodel is constructed, it just creates the Command and sets the command's MyViewmodel property so that the command can get a reference to which instance to update when the command is triggered.

    When the command is executed, it just sets its viewmodel ListboxActualWidth property to the new ActualWidth.

    When the button is pressed, it gets addressability to the viewmodel through the layoutRoot's DataContext and then shows the ListboxActualWidth.

    The Delphi Prism code splits the interface and implementation into separate sections (which I LOVE because you can get a sense of what the object IS before having to look at any real code).

    Also Delphi Prism has a "notify" directive.  That takes care of all the INotifyPropertyChanged stuff without any more "fluff" in the code.

    Hope this helps.

    Tuesday, January 17, 2012 11:27 AM
  • Sorry for the delay....thanks ever so muh for your help. The reason it wasn't working for me was that I was trying to pass the ActualWidth as the command parameter - that doesn't seem to work. Passing the ListBox itself as the command parameter, casting to FrameworkElement and accessing ActualWidth works fine.

    Thanks again :)

    Tuesday, January 24, 2012 4:26 AM