Question

This is my first post on StackOverflow and also my first Question. I had created a UserControl in WPF with the MVVM Pattern (called Block). This UserControl should be used in another UserControl, as DataTemplate of a ListBox (called Sector).

The Block Control should be used as standalone and as DataTemplate for different UserControls.

If I set the DataContext in the View of Block, the standalone Version works great, but not as part of the Sector View.

If I don´t set the DataContext in the View of Block, it works in Sector but don’t standalone.

My question is, is it the only way to leave the DataContext in the View of Block and set it in the View I used the Control or the ViewModel?

Here is my Code:

Model of Block:

public class BlockModel : ModelBase
{
    #region private Variables

    string blockName = "Block";
    string blockContent = "00000";

    #endregion private Variables

    #region Properties

    public string BlockName
    {
        get { return blockName; }
        set { blockName = value; NotifyPropertyChange("BlockName "); }
    }

    public string BlockContent
    {
        get { return blockContent; }
        set { blockContent = value; NotifyPropertyChange("BlockContent"); }
    }

    #endregion Properties

    #region ctor

    public BlockModel () { }

    #endregion ctor
}

ViewModel of Block:

public class BlockViewModel : ViewModelBase
{
    #region private Variables

    string charToFill = "0";
    DelegateCommand fillWithChar;
    BlockModel dataModel = new BlockModel();

    #endregion private Variables

    #region Properties

    public Models. BlockModel DataModel
    {
        get { return dataModel; }
        set { dataModel = value; }
    }

    public string BlockContent
    {
        get { return dataModel. BlockContent; }
        set
        {
            if (dataModel. BlockContent != value)
            {
                dataModel. BlockContent = value;
                NotifyPropertyChange ("BlockContent");
            }
        }
    }

    public string BlockName
    {
        get { return dataModel. BlockName; }
        set
        {
            if (dataModel. BlockName != value)
            {
                dataModel. BlockName = value;
                NotifyPropertyChange("BlockName");
            }
        }
    }


    public string CharToFill
    {
        get { return charToFill; }
        set
        {
            if (charToFill != value)
            {
                charToFill = value;
                NotifyPropertyChange ("CharToFill");
            }
        }
    }


    public ICommand FillWithChar
    {
        get
        {
            if (fillWithChar == null)
                fillWithChar = new DelegateCommand(FillText);
            return fillWithChar;
        }
    }

    #endregion Properties

    #region ctor

    public BlockViewModel()
    {

    }

    #endregion ctor

    #region Methods

    private void FillText()
    {
        CodingBlockContent = CodingBlockContent.PadLeft(32, charToFill[0]);
    }

    #endregion Methods

}

View of Block:

<UserControl x:Class="…./Block"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:…/Controls"
         mc:Ignorable="d" d:DesignWidth="460" d:DesignHeight="44">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="124" />
        <ColumnDefinition Width="301" />
        <ColumnDefinition Width="30"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="30"></RowDefinition>

    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding BlockName}" 
               HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6"/>
    <ComboBox FontFamily="Consolas" Grid.Row="00" Grid.Column="1" 
              Text="{Binding BlockContent, Mode=TwoWay}" 
              Tag="{Binding BlockName}" Name="cbxBlock" FontSize="14" 
              HorizontalAlignment="Left" VerticalAlignment="Center" Width="290" Margin="1,4,0,5" IsEditable="True" Height="26">
        <ComboBoxItem Content=""></ComboBoxItem>
        <ComboBoxItem Content="0000000000000"></ComboBoxItem>
        <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
            <TextBox Margin="10,2,0,2" Text="0" Width="24" FontSize="14" Name="tbxChar" MaxLength="1"></TextBox>
            <TextBlock Margin="10,2,0,2" Text="auffüllen" VerticalAlignment="Center" FontSize="14"></TextBlock>
            <Button Margin="10,2,0,2" Content="Ausführen" Width="100"  Command="{Binding FillWithChar}"></Button>
        </StackPanel>
    </ComboBox>
    <TextBlock Grid.Row="0" Width="30" Grid.Column="2" Text="{Binding CodingBlockContent.Length}" 
               HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" Margin="2,6,0,6" Grid.ColumnSpan="2"/>
</Grid>

Model of Sector:

public class SectorModel
{
    #region private Variables

    int blockAmount = 4;
    string sectorNumber = "Sektor";
    int blockBegin = 0;
    bool isSectorInUse = false;

    #endregion private Variables

    #region Properties

    public string SectorNumber
    {
        get { return sectorNumber; }
        set { sectorNumber = value;}
    }

    public int BlockBegin
    {
        get { return blockBegin; }
        set
        {
            blockBegin = value;
        }
    }

    public bool IsSectorInUse
    {
        get { return isSectorInUse; }
        set { isSectorInUse = value;}
    }

    public int BlockAmount
    {
        get { return blockAmount; }
        set
        {
            blockAmount = value;;
        }
    }

    #endregion Properties

  public SectorModel()
    {
    }
}

ViewModel of Sector:

public class SectorViewModel : ViewModelBase
{
    #region private Variables

    SectorModel dataModel = new SectorModel();
    ObservableCollection<BlockViewModel> sectorBlocks = new ObservableCollection<BlockViewModel>();

    #endregion private Variables

    #region Properties

    public SectorModel DataModel
    {
        get { return dataModel; }
        set { dataModel = value; }
    }

    public ObservableCollection<BlockViewModel> SectorBlocks
    {
        get { return sectorBlocks; }
        set { sectorBlocks = value; NotifyPropertyChange ("SectorBlocks"); }
    }

    public string SectorNumber
    {
        get { return "Sektor " + DataModel.SectorNumber; }
        set { DataModel.SectorNumber = value; NotifyPropertyChange ("SectorNumber"); }
    }

    public int BlockBegin
    {
        get { return DataModel.BlockBegin; }
        set
        {
            DataModel.BlockBegin = value;
            SetBlocks();
            OnPropertyChanged("BlockBegin");
        }
    }

    public bool IsSectorInUse
    {
        get { return DataModel.IsSectorInUse; }
        set { DataModel.IsSectorInUse = value; NotifyPropertyChange ("IsSectorInUse"); }
    }

    public int BlockAmount
    {
        get { return DataModel.BlockAmount; }
        set
        {
            DataModel.BlockAmount = value;
            SetBlocks();
            NotifyPropertyChange ("CodingBlockAmount");
        }
    }

    #endregion Properties

    void SetBlocks()
    {
        while (SectorBlocks.Count != BlockAmount)
        {
            SectorBlocks.Add(new BlockViewModel());
        }

        int begin = BlockBegin;

        foreach (BlockViewModel block in SectorBlocks)
        {
            block.CodingBlockName = "Block " + begin.ToString().PadLeft(2, '0');
            block++;
        }
    }

    public SectorViewModel() 
    {
        SetBlocks();
    }
}

View of Sector:

<UserControl xmlns:Views="clr-namespace:…/RFIDControls"  x:Class="…/Sector"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="473">
<Border BorderBrush="Black" BorderThickness="0,0,0,1">
    <Expander Name="expMain" Margin="0,0,4,0">
        <Expander.Header>
            <Grid Name="grdHeader">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100" />
                    <ColumnDefinition Width="300" />
                    <ColumnDefinition Width="30" />
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Grid.Row="0" Height="24" Text="{Binding SectorNumber}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="14" FontWeight="Bold" Panel.ZIndex="98"/>
                <CheckBox Grid.Column="2" VerticalAlignment="Center" IsChecked="{Binding IsSectorInUse}"></CheckBox>
            </Grid>
        </Expander.Header>
        <Grid>
            <ListBox ItemsSource="{Binding SectorBlocks}" Name="listBox">
                <ListBox.ItemTemplate>
                    <DataTemplate DataType="{x:Type Views:BlockViewModel}">
                        <Views:Block/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Expander>
</Border>

Thank you.

With kind regards from Germany

Dominik

PS: I hope my English is not so bad as I suppose

Was it helpful?

Solution

This is a common problem for new users of WPF. You have two possible solutions. When using MVVM, you shouldn't need to set the DataContext of the UserControl anywhere. Instead, you can simply add a DataTemplate to your App.xaml to make the pairing:

<DataTemplate DataType="{x:Type ViewModels:BlockViewModel}">
    <Views:BlockView />
</DataTemplate>

In this way, whenever you show an instance of your BlockViewModel, the Framework will display the related BlockView instead (like you have done in your SectorView class).

The alternative option is to not use MVVM for the UserControl (use Bindable DependencyPropertys instead), to not set its DataContext internally and to use RelativeSource Bindings on the elements inside instead:

<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding DataContext.BlockName, 
    RelativeSource={RelativeSource AncestorType={
    x:Type YourXmlNamespacePrefix:BlockView}}}" HorizontalAlignment="Left" 
    VerticalAlignment="Center" FontSize="14" Margin="2,6"/>

Using this method, the RelativeSource Binding will look at whatever object is currently set as the DataContext.

OTHER TIPS

Sounds like Block act as an control, which seems participate in other usercontrol or part of Template, in my opinion, mvvm doesn't work in here, you should use Dependency property instead. Forgive my poor english.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top