Working on a ComboBox that displays a list of available tile backgrounds. This is just a simple ComboBox with an ItemSource set to a collection of MapTileBackground objects.
The MapTileBackground class is defined entirely with properties:
public partial class MapTileBackground
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public byte[] Content { get; set; }
public Nullable<int> Color { get; set; }
public int StrokeColor { get; set; }
public byte StrokeThickness { get; set; }
}
which is defined in a separate library and I would prefer to not change it.
I have defined a simple shape extension to draw the background::
public class MapTileBackgroundPreview : Shape
{
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Point), typeof(MapTileBackgroundPreview));
public static readonly DependencyProperty TileBackgroundProperty = DependencyProperty.Register("TileBackground", typeof(MapTileBackground), typeof(MapTileBackgroundPreview));
public MapTileBackgroundPreview()
{
layout = new Hex.Layout(Hex.Orientation.Flat, new Hex.Point(8, 8), new Hex.Point(4, 4));
Size = new Point(8, 8);
TileBackground = null;
}
private Hex.Layout layout;
protected override Geometry DefiningGeometry
{
get
{
var points = layout.HexCorners(0, 0).ToArray();
var path = new PathFigure();
path.StartPoint = points[5].ToWin();
for (var i = 0; i < 6; i++)
path.Segments.Add(new LineSegment(points[i].ToWin(), true));
var geo = new PathGeometry();
geo.Figures.Add(path);
return geo;
}
}
public Point Size
{
get
{
return (Point)GetValue(SizeProperty);
}
set
{
SetValue(SizeProperty, value);
layout.Size = value.ToHex();
layout.Origin = new Hex.Point(layout.Size.X / 2, layout.Size.Y / 2);
}
}
public MapTileBackground TileBackground
{
get
{
return (MapTileBackground)GetValue(TileBackgroundProperty);
}
set
{
SetValue(TileBackgroundProperty, value);
if (value == null)
{
Fill = Brushes.Transparent;
Stroke = Brushes.Black;
StrokeThickness = 1;
}
else
{
Stroke = value.Stroke();
StrokeThickness = value.StrokeThickness();
Fill = value.Fill(layout.Orientation);
}
}
}
}
The layout is just a conversion utility between screen pixel coordinates and a hexagonal system. DefiningGeometry just add 6 line segments of the hex. The TileBackground setter, when given a not null MapTileBackground, updates the Stroke and Fill as the background defines. I've tested this control successfully (outside the combo box data template).
And speaking of:
<DataTemplate x:Key="TileListItemRenderer">
<Grid Width="225">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackground="{Binding /}"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
So I just create a shape, and two labels, bind the shape to the current MapTileBackground object (combo box ItemSource is a collection of MapTileBackground objects), and the labels to Name and Description.
My problem is the shape is always drawn empty (as in TileBackground is null) and the setter is never invoked. Both the Name Label and Description TextBlock behave as expected (display correct text). And during my debugging attempts, I created an id property on the preview object which in turn invokes the TileBackground Setter and bound it to the Id property (avoid current object bind), again, the TileBackgroundId setter is never invoked. I even added a new label bound to Id to see if that was working and it displays the id as expected. Here are those changes that again did not work. The TileBackgroundId or TileBackground properties are never set when opening the drop down.
<DataTemplate x:Key="TileListItemRenderer">
<Grid Width="225">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackgroundId="{Binding Id}"/>
<Label Grid.Row="0" Grid.Column="0" Content="{Binding Id}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview));
public int TileBackgroundId
{
get
{
return (int)GetValue(TileBackgroundIdProperty);
}
set
{
SetValue(TileBackgroundIdProperty, value);
TileBackground = TMapTileBackgroundTool.Get(value);
}
}
TMapTileBackgroundTool.Get() returns the correct object based on Id.
I have also tested instances of MapTileBackgroundPreview setting TileBackgroundId outside the data template.
Any thoughts as to what is going on?
The setter of the CLR wrapper for the dependency property is not supposed to be set as the WPF binding engine calls the GetValue and SetValue methods directly:
Setters not run on Dependency Properties?
Why are .NET property wrappers bypassed at runtime when setting dependency properties in XAML?
The getter and setter of the CLR wrapper property should only call the GetValue and SetValue method respectively.
If you want to do something when the dependency property is set, you should register a callback:
public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview),
new PropertyMetadata(0, new PropertyChangedCallback(TileBackgroundIdChanged)));
public int TileBackgroundId
{
get
{
return (int)GetValue(TileBackgroundIdProperty);
}
set
{
SetValue(TileBackgroundIdProperty, value);
}
}
private static void TileBackgroundIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MapTileBackgroundPreview ctrl = (MapTileBackgroundPreview)d;
ctrl.TileBackground = TMapTileBackgroundTool.Get((int)e.NewValue);
}
Related
I'm working on a UWP App using the Template 10, and I don't manage to get the UI updated after a property is changed in the ViewModel. I tried to implement the Bindable base at the Model, but still doesn't work.
XAML:
<Page.DataContext>
<vm:RoomPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid x:Name="RoomProperties"
RelativePanel.Below="pageHeader"
RelativePanel.AlignLeftWithPanel="True">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Image Grid.Column="0" Width="220" Height="220" Stretch="Fill" Source="{x:Bind ViewModel.Room.Image}"></Image>
<TextBlock Grid.Column="1" FontSize="16" Text="{x:Bind ViewModel.Room.Name}"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" FontSize="16" Text="Room Type: "></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="1" FontSize="16" Text="{x:Bind ViewModel.Room.Type}"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="2" FontSize="16" Text="Room Number: "></TextBlock>
<TextBlock Grid.Column="1" Grid.Row="2" FontSize="16" Text="{x:Bind ViewModel.Room.Number}"></TextBlock>
</Grid>
<ListView x:Name="SensorListView"
ItemsSource="{x:Bind ViewModel.Room.Sensors}"
IsEnabled="False"
RelativePanel.Below="RoomProperties"
RelativePanel.AlignLeftWithPanel="True">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:Sensor">
<StackPanel HorizontalAlignment="Left">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="16" Text="{x:Bind Name}"></TextBlock>
<TextBlock Grid.Column="1" FontSize="16" Text="{x:Bind SensorValues[0].Value, Mode=TwoWay}"></TextBlock>
<TextBlock Grid.Column="2" FontSize="16" Text="{x:Bind Units}"></TextBlock>
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel:
public class RoomPageViewModel : ViewModelBase
{
Template10.Services.SerializationService.ISerializationService _SerializationService;
private FileIOHelper.FileIOHelper fileIOHelper = new FileIOHelper.FileIOHelper();
private AppServiceConnection serialCommandService;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
public RoomPageViewModel()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
Room = Room.CreateNewRoom();
}
}
private Room room = Room.CreateNewRoom();
public Room Room
{
get
{
return this.room;
}
set
{
Set(ref room, value);
}
}
public void UpdateRoom()
{
foreach (var sensor in Room.Sensors)
{
var sensorValue = new SensorValue();
sensorValue.Sensor = "R" + Room.Number + "D" + sensor.DeviceNumber + "S" + sensor.Type;
ObservableCollection<SensorValue> newList = fileIOHelper.ReadFromFile(sensorValue).ToObservableCollection();
SensorValue newSensorValue = newList.Last();
sensor.SensorValues = new ObservableCollection<SensorValue> { newSensorValue };
}
foreach (var actuator in Room.Actuators)
{
var actuatorValue = ActuatorValue.CreateNewActuatorValue();
actuatorValue.Actuator = "R" + Room.Number + "D" + actuator.DeviceNumber + "A" + actuator.Type;
ObservableCollection<ActuatorValue> newList = fileIOHelper.ReadFromFile(actuatorValue).ToObservableCollection();
ActuatorValue newActuatorValue = newList.Last();
actuator.ActuatorValues = new ObservableCollection<ActuatorValue> { newActuatorValue };
}
}
public async void RefreshButton_Click(object sender, object parameter)
{
Random rnd = new Random();
Room = Room.CreateNewRoom(rnd.Next(1, 9));
//UpdateRoom();
await Task.CompletedTask;
}
Model:
public class Room : BindableBase
{
private string name;
private string image;
private string type;
private int number;
public string Name
{
get
{
return name;
}
set
{
Set(ref name, value);
}
}
public string Image
{
get
{
return image;
}
set
{
Set(ref image, value);
}
}
public string Type
{
get
{
return type;
}
set
{
Set(ref type, value);
}
}
public int Number
{
get
{
return number;
}
set
{
Set(ref number, value);
}
}
private ObservableCollection<Sensor> sensors;
private ObservableCollection<Actuator> actuators;
public ObservableCollection<Sensor> Sensors
{
get
{
return sensors;
}
set
{
Set(ref sensors, value);
}
}
public ObservableCollection<Actuator> Actuators
{
get
{
return actuators;
}
set
{
Set(ref actuators, value);
}
}
public Room() {
Random rnd = new Random();
Name = "DefaultName";
Image = "DefaultImage";
Type = "DefaultType";
Number = rnd.Next(1,9);
Sensors = new ObservableCollection<Sensor>();
Actuators = new ObservableCollection<Actuator>();
}
public Room(int inputNumber)
{
Name = "DefaultName";
Image = "DefaultImage";
Type = "DefaultType";
Number = inputNumber;
Sensors = new ObservableCollection<Sensor>();
Actuators = new ObservableCollection<Actuator>();
}
public static Room CreateNewRoom(int inputNumber)
{
return new Room(inputNumber);
}
}
I used this guide for documentation for the implementation (https://github.com/Windows-XAML/Template10/wiki/MVVM). Any idea on why the UI is not getting updated? Thanks.
A mistake most people (including myself) often make when being used to the 'old' Binding syntax is that x:Bind has OneTime binding as default instead of OneWay binding.
Mode: Specifies the binding mode, as one of these strings: "OneTime", "OneWay", or "TwoWay". The default is "OneTime". Note that this differs from the default for {Binding}, which is "OneWay" in most cases.
Source: MSDN
What you need for your binding updates to work is:
Using INotifyPropertyChanged, this is handled by BindableBase.
Set the correct mode, e.g.
Text="{x:Bind ViewModel.Room.Number, Mode=OneWay}
I am making a user control to represent chosen numbers (like in a lottery). The problem is that when binding to it inside a data template binding does not work.
It works correclty when hardcoding the values.
The errors are of this type and they appear for every dependency property I bind to
Error: BindingExpression path error: 'BackCheckedColor' property not found on 'NumberControlTest.Controls.NumberControl'. BindingExpression: Path='BackCheckedColor' DataItem='NumberControlTest.Controls.NumberControl'; target element is 'NumberControlTest.Controls.NumberControl' (Name='null'); target property is 'CheckedBackgroundColor' (type 'String')
What I find strange is that in this section of the error
BindingExpression: Path='BackCheckedColor' DataItem='NumberControlTest.Controls.NumberControl'
It suggests that it is trying to find the BackCheckedColor in the usercontrol itself. That does not make sense to me. Can somebody help??
User Control Xaml
<UserControl.Resources>
<local:CheckedToBrushConverter x:Key="CheckedToBrushConverter"
CheckedBackgroundColor="{Binding CheckedBackgroundColor}"
CheckedForegroundColor="{Binding CheckedForegroundColor}"
UncheckedBackgroundColor="{Binding UncheckedBackgroundColor}"
UncheckedForegroundColor="{Binding UncheckedForegroundColor}"/>
</UserControl.Resources>
<Grid Tapped="Grid_Tapped">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16*"/>
<ColumnDefinition Width="130*"/>
<ColumnDefinition Width="16*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="16*"/>
<RowDefinition Height="130*"/>
<RowDefinition Height="30*"/>
</Grid.RowDefinitions>
<Ellipse x:Name="Ellipse" Grid.RowSpan="3" Grid.ColumnSpan="3" Fill="{Binding IsChecked, Converter={StaticResource CheckedToBrushConverter}, ConverterParameter=background}"/>
<Viewbox Grid.Row="1" Grid.Column="1">
<TextBlock x:Name="NumberBlock" TextWrapping="Wrap" FontFamily="Segoe UI" Text="{Binding NumberValue}" Foreground="{Binding IsChecked, Converter={StaticResource CheckedToBrushConverter}, ConverterParameter=foreground}" />
</Viewbox>
</Grid>
User control code behind
public sealed partial class NumberControl : UserControl
{
public NumberControl()
{
this.InitializeComponent();
this.DataContext = this;
}
public string UncheckedBackgroundColor
{
get { return (string)GetValue(UncheckedBackgroundColorProperty); }
set { SetValue(UncheckedBackgroundColorProperty, value); }
}
// Using a DependencyProperty as the backing store for UncheckedBackgroundColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UncheckedBackgroundColorProperty =
DependencyProperty.Register("UncheckedBackgroundColor", typeof(string), typeof(NumberControl), new PropertyMetadata(string.Empty));
public string CheckedBackgroundColor
{
get { return (string)GetValue(CheckedBackgroundColorProperty); }
set { SetValue(CheckedBackgroundColorProperty, value); }
}
// Using a DependencyProperty as the backing store for CheckedBackgroundColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CheckedBackgroundColorProperty =
DependencyProperty.Register("CheckedBackgroundColor", typeof(string), typeof(NumberControl), new PropertyMetadata(string.Empty));
plus more dependency properties like those.
MainPage xaml
<Page.Resources>
<DataTemplate x:Key="NumberTemplate">
<Grid>
<controls:NumberControl
UncheckedBackgroundColor="{Binding BackUncheckedColor}"
UncheckedForegroundColor="{Binding ForeUncheckedColor}"
CheckedBackgroundColor="{Binding BackCheckedColor}"
CheckedForegroundColor="{Binding ForeCheckedColor}"
NumberValue="{Binding Value}"
IsChecked="{Binding IsChecked}"
HorizontalAlignment="Center"
VerticalAlignment="Center" Width="45" Height="45"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="#0f455f">
<GridView x:Name="NumbersGridView" ItemTemplate="{StaticResource NumberTemplate}" ItemsSource="{Binding Numbers, Mode=TwoWay}"/>
<Button x:Name="printButton" Content="Print" VerticalAlignment="Bottom" HorizontalAlignment="Center" Click="printButton_Click"/>
</Grid>
Model class which provides the data of the collection bound to the gridview
public class MockNumber
{
public MockNumber(bool isChecked, int value, string backchcolor, string forchcolor, string backunchcolor, string forunchcolor)
{
IsChecked = isChecked;
Value = value;
BackCheckedColor = backchcolor;
ForeCheckedColor = forchcolor;
BackUncheckedColor = backunchcolor;
ForeUncheckedColor = forunchcolor;
}
public bool IsChecked { get; set; }
public int Value { get; set; }
public string BackCheckedColor { get; set; }
public string ForeCheckedColor { get; set; }
public string BackUncheckedColor { get; set; }
public string ForeUncheckedColor { get; set; }
}
EDIT: How the model is instantiated and bound in the MainPage codebehind.
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
makelist();
}
void makelist()
{
for (int i = 1; i <= 20; i++)
{
Numbers.Add(new MockNumber(i % 4 == 0 ? true : false, i, "#dead2b", "#000000", "#dead2b", "#f0b60c"));
}
}
private ObservableCollection<MockNumber> numbers = new ObservableCollection<MockNumber>();
public ObservableCollection<MockNumber> Numbers
{
get
{
return numbers;
}
set
{
numbers = value;
}
}
The reason why it's trying to find the 'BackCheckedColor' property from the NumberControl is because you set the user control's datacontext to itself.
public NumberControl()
{
this.InitializeComponent();
this.DataContext = this;
}
You're telling the user control that your data context is itself. It means that when you do the "{Binding}" the path should be a property of the user control which I don't think is a good idea.
I understand that you want to bind some dependency properties to your Model class but I didn't see in your example where you instantiated the model class and use it as your data context.
Another thing to consider, you might want to use a custom control instead of a user control. I can see that you added some dependency properties to your user control but in practice, dependency properties added to custom controls and static classes that has attached properties.
EDIT:
After reading your additional code, I can see that the user control's datacontext was being set to 'this' which is itself. You need to remove that.
public sealed partial class NumberControl : UserControl
{
public NumberControl()
{
this.InitializeComponent();
this.DataContext = this; //Remove this line
}
//...
Then after removing that, you usercontrol should inherit the GridViewItem's Binding or you can explicitly put the datacontext in your DataTemplate.
<DataTemplate x:Key="NumberTemplate">
<Grid>
<controls:NumberControl DataContext="{Binding}" <!--specify the data context-->
UncheckedBackgroundColor="{Binding BackUncheckedColor}"
//..
I have the following ListView:
<ListView
Grid.Row="1"
ItemsSource="{Binding MeditationDiary}" >
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="{Binding ElementName=ListViewHeaders, Path=ActualWidth}" >
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding StartTime}" />
<TextBlock
Grid.Column="1"
Text="{Binding TimeMeditated}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is bound to a MeditationDiary property of type MeditationDiary consisting of MeditationEntries:
public class MeditationDiary : Collection<MeditationEntry> { }
public class MeditationEntry
{
public DateTime StartTime { get; set; }
public TimeSpan TimeMeditated { get; set; }
}
The ListView binds to this MeditationDiary:
private MeditationDiary _meditationDiary;
public MeditationDiary MeditationDiary
{
get
{
RaisePropertyChanged(nameof(MeditationDiary));
return _meditationDiary;
}
set
{
_meditationDiary = value;
RaisePropertyChanged(nameof(MeditationDiary));
}
}
Strangely enough when I assign a new MeditationDiary object to the property (which contains data with MeditationEntries) the ListView does no longer display data.
I'm assigning the new MeditationDiary object in the UpdateDiary method which is called after adding an entry:
private async void UpdateDiary()
{
var latestDiary = await _repository.GetAsync();
MeditationDiary = latestDiary;
}
Why can this be and how can it be fixed?
This is most likely the culprit that's messing up your binding:
MeditationDiary = latestDiary;
Instead, try clearing out the current collection and then adding the new values to it:
MeditationDiary.Clear();
foreach (var entry in latestDiary)
MeditationDiary.Add(entry);
You'll probably have to call RaisePropertyChanged on the collection after you add the new items.
As a side note, you could replace MeditationDiary with an ObservableCollection<MeditationEntry>, which automatically notifies the UI when you add/remove items in it.
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
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.
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.
I have a UserControl which acts as a wrapper for a ContentControl, which is simply a title to the ContentControl.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Background="Green" Grid.Row="0">
<TextBlock Text="{Binding Header}" Style="{StaticResource HeaderStyle}" Margin="12, 10, 0, 10" />
</Grid>
<ContentControl HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Content="{Binding Body}" Grid.Row="1"/>
</Grid>
And here's where I try to use the control:
<gbl:ListHeader Grid.Row="1" Visibility="{Binding HasMovies, Converter={StaticResource VisibilityConverter}}" Header="{Binding Path=LocalizedResources.movie_list_header, Source={StaticResource LocalizedStrings}}" >
<gbl:ListHeader.Body>
<ListBox SelectionChanged="ListBoxContainerSelectionChanged" ItemsSource="{Binding Movies}" ItemContainerStyle="{StaticResource HeaderListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<gbl:MovieItemControl Header="{Binding MovieTitle}" Description="{Binding FormattedDescription}" Detail="{Binding FormattedDetail}" Opacity="{Binding IsSuppressed, Converter={StaticResource DimIfTrueConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</gbl:ListHeader.Body>
The DataBinding to the list happens, however nothing displays in the control. I'm guessing that it's still there, but too small to see (undefined h/w).
Is there something that I'm doing wrong? The header shows fine, so the control appears to be working somewhat.
Edit:
Here's the code-behind for ListHeader:
public partial class ListHeader : UserControl
{
private readonly ListHeaderData _data = new ListHeaderData();
public ListHeader()
{
InitializeComponent();
DataContext = _data;
}
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
// Using a DependencyProperty as the backing store for Header. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(ListHeader), new PropertyMetadata("",HeaderPropertyChanged) );
private static void HeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var lh = d as ListHeader;
if (lh != null)
lh._data.Header = e.NewValue as string;
}
public object Body
{
get { return GetValue(BodyProperty); }
set { SetValue(BodyProperty, value); }
}
// Using a DependencyProperty as the backing store for Body. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BodyProperty =
DependencyProperty.Register("Body", typeof(object), typeof(ListHeader), new PropertyMetadata(null, BodyPropertyChanged));
private static void BodyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var lh = d as ListHeader;
if (lh != null)
lh._data.Body = e.NewValue;
}
}
public class ListHeaderData : ViewModelBase
{
public ListHeaderData()
{
if (IsInDesignMode)
{
Header = "Custom Header Goes Here";
Body = new Grid() { Background = new SolidColorBrush(Colors.Yellow) };
}
}
private string _header;
public string Header
{
get { return _header; }
set { _header = value; RaisePropertyChanged("Header"); }
}
private object _body;
public object Body
{
get { return _body; }
set { _body = value; RaisePropertyChanged("Body");}
}
}
In addition to what i said in my comment you appear to bind to your DataContext in the UserControl declaration which is a Bad Thing and the problem of all this.
You appear to want to bind to the properties of the UserControl but you bind directly to the properties of the DataContext which is your ViewModel, hence setting the Body property on an instance in XAML does nothing as the property is sidestepped by the internal binding.
UserControls should for all i know do bindings like this:
<UserControl Name="control" ...>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Background="Green" Grid.Row="0">
<TextBlock Text="{Binding Header, ElementName=control}" Style="{StaticResource HeaderStyle}" Margin="12, 10, 0, 10" />
</Grid>
<ContentControl HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Content="{Binding Body, ElementName=control}" Grid.Row="1"/>
</Grid>
Get rid of those dependency property changed callbacks and change the property code in ViewModels to this format to make sure it changed:
private int _MyProperty = 0;
public int MyProperty
{
get { return _MyProperty; }
set
{
if (_MyProperty != value)
{
_MyProperty = value;
OnPropertyChanged("MyProperty");
}
}
}