3
votes

It's my first time using the MVVM pattern and I have a bit of trouble understanding how everything ties together.

I have a UserControl with a Textbox element which should change the Width of said UserControl based on it's input.

I'm facing two problems:

  1. For my idea to work, I need to change and bind to d:DesignWidth and my ColumnDefinition Width. How and where do I implement those changes? Based on my knowledge of MVVM the View (in this case my UserControl) is controlled by a ViewModel for said UserControl. Is it nessesary to implement one or is it possible to bind directly to both properties? I know I can name my ColumnDefinition with x:Name="MyColumnDefinition" but is the same possible for the actual UserControl Width?

             mc:Ignorable="d" 
             d:DesignHeight="60" d:DesignWidth="170">
    
  2. I have an ObservableCollection filled with two different UserControls and I want the Controls not to overlap when I display them. I use a ListBox element to display the ObservableCollection and implement the different UserControls over DataTemplates with a DataTemplateSelector. This works fine now but I'm worried if I dynamically change the Control Width that it will just overlap the next Control in the list. How do I ensure this won't happen?

Below is the code I have for now for the UserControl:

<Border Background="LightGray" CornerRadius="6">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50"/>
            <ColumnDefinition Width="70"/>
            <ColumnDefinition Width="50"/>
        </Grid.ColumnDefinitions>

        <Button VerticalAlignment="Top" HorizontalAlignment="Right" Grid.Column="2" Grid.Row="0" 
                BorderThickness="0" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" 
                Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DeleteCommand}"
                CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DeleteCommandParameter}">
            <Rectangle Width="8" Height="8" Fill="White">
                <Rectangle.OpacityMask>
                    <VisualBrush Visual="{StaticResource appbar_close}" Stretch="Fill" />
                </Rectangle.OpacityMask>
            </Rectangle>
        </Button>

        <TextBlock Grid.Column="1" Grid.Row="0" FontSize="12" Margin="0,4,0,18" Foreground="White" HorizontalAlignment="Center" Grid.RowSpan="2">Delay</TextBlock>

        <TextBox Grid.Column="1" Grid.Row="1" Width="46" Margin="0,4,0,16" HorizontalAlignment="Center" Grid.RowSpan="2" 
                 Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Delay.MiddlePosition, UpdateSourceTrigger=PropertyChanged}"></TextBox>

        <TextBlock Grid.Column="1" Grid.Row="2" FontSize="8" Margin="20,5,20,5" Foreground="Gray" HorizontalAlignment="Center">[s]</TextBlock>
    </Grid>
</Border>

Edit:

ListBox-XAML to hold the other UserControls (I'm trying to build an Axis which can be filled with custom Positioning- and DelayControls:

<ListBox Name="Test" SelectionMode="Single" Grid.Column="1"
                 ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=BlockList}"
                 ItemTemplateSelector="{StaticResource BlockTemplateSelector}">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="Focusable" Value="False"/>
                </Style>
            </ListBox.ItemContainerStyle>
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel IsItemsHost="True" Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>

End result should look kind of like this, but with differently sized Positioning and Delay blocks:

XZY axes with different Pos/Delay Commands

2
mc:Ignorable="d" is a namespace that is ignored at real time. It's only purpose is to modify something at design time. You can completely remove it for what is worth. Also note that in WPF grids, you can use * and Auto value for your width and height. * takes all the available space, Auto take the minimum required space. Try to avoid fixing you width/height if you want resizable appDaniele Sartori
Also please, add the xaml of your listboxDaniele Sartori
As far as i know designwidth and height are only for the designer, so changing them would have no impact on your actual running app later. That said, have you tried to bind the width of your controls to properties and change them with your textbox? Like: Bind the with of your UserControl to a property, set the default with of your property before calling InitializeComponent, once you confirmed your input (with a button or a enter click event), try parse your textbox text to a double and change your widht property to this value if successful. Not sure how 'clean' this way would be, howerver.Azzarrel
Thanks for the tips and the good ideas @DanieleSartori and @Azzarrel! I will change the fixed width and height of my UserControl. XAML of the ListBox was added.jan97con
As far as i can tell, the simpliest things that comes to my mind is to have your ObservableCollection "BlockList" to be a collection of Obejects (if it's not like that already). These Objects contain all the properties you have already, plus a width Property. If your block is of type Delay, then the width is calculated based on "MiddlePosition" property. What you have to do then is to bind Your Border Width to this propertyDaniele Sartori

2 Answers

0
votes

I struggeled quite a while to figure out how to address your issue and even though I am not completely happy with the outcome, I managed to solve it.

First I create a ListBox with a DummyList, which contains Model-Objects called 'UserControlModel' with a singe Property 'modelWidth', from which I create my UserControls with their default size.

        <ListBox ItemsSource="{Binding SimpleList, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Width="Auto" Height="200">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <osv:UserControl1 Width="{Binding modelWidth}" OnTextValidated="UserControlSizeChangeEvent"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

OnTextValidated is a RoutedEvent to hand up the KeyDown-Event from my Textbox to my Window(which i will show later) The UserControl1.xaml then adds the textbox

 <TextBox Width="60" Height="30" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" KeyDown="TextBox_KeyDown" Text="{Binding myText, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"></TextBox>

with a KeyDown event and a textbinding.

    private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
  if (e.Key == Key.Return)//press enter to change
  {
    if (double.TryParse(myText, out double d) == true)
    {
      if (d >= 50) //minimum width, so i won't set it to 0 by accident
      {
        myWidth = d; //set the new Width
        OnTextValidated(this, new RoutedEventArgs()); //tell window to resize the UserControl
      }

    }
  }
}

Once I validated the new size is neither wrong nor too small i call a RoutedEventHandler

    private RoutedEventHandler m_OnTextValidated;
/// <summary>
/// 
/// </summary>
public RoutedEventHandler OnTextValidated
{
  get { return m_OnTextValidated; }
  set
  {
    m_OnTextValidated = value;
    OnPropertyChanged("CustomClick");
  }
}

now i can bind on this like shown above.

next i have to do is passing down my event from the xaml.cs to the MinWindowViewModel

    //Variables
    private MainWindowViewModel m_DataContext;

//Constructor
DataContext = new MainWindowViewModel ();
m_DataContext = (MainWindowViewModel)this.DataContext;



        private void UserControlSizeChangeEvent(object sender, RoutedEventArgs e)
    {
        if (m_DataContext != null)
        {
            m_DataContext.UserControlSizeChangeEvent(sender, e);
        }
    }

and finally update the size of my object in my code behind

  public void UserControlSizeChangeEvent(object sender, RoutedEventArgs e)
{
  UserControl1 uc = sender as UserControl1;
  uc.Width = uc.myWidth;
}

Note: Although this works quite fine i'd be much happier if i found a way to change the width of the model instead of the object, so they would still have the same width in case the list is redrawn. I also didn't use a MVVM pattern for my UserContrl, so you'll have to pass the event from your xaml.cs to your viewmodel first like I did for the MainWindow

-1
votes

Check this code will help you to set width of one control to other control.

<Border>
    <Grid x:Name="grv">
        <TextBox  Width="{Binding ElementName=grv,
                  Path=ActualWidth}">
            </TextBox>
    </Grid>
</Border>