I'm making a custom control. It uses some libraries from NuGet, namely:
PanAndZoom
Wpf.Controls.PanAndZoom
Wpf.MatrixExtentions
There is only the usercontrol xaml, its code behind and a separate class in the entire project. They're as below:
<UserControl x:Class="DesignSurface"
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:paz="clr-namespace:Wpf.Controls.PanAndZoom;assembly=Wpf.Controls.PanAndZoom"
xmlns:local="clr-namespace:stigzler.controls.WPF.Workspace"
mc:Ignorable="d" Focusable="True"
d:DesignHeight="450" d:DesignWidth="800" Loaded="UserControl_Loaded">
<paz:ZoomBorder>
<Canvas x:Name="DesignCanvas" Background="DarkOliveGreen"></Canvas>
</paz:ZoomBorder>
</UserControl>
Code behind:
Public Class DesignSurface
Public Sub AddDesignItem(di As DesignItem)
DesignCanvas.Children.Add(di)
Canvas.SetLeft(di, di.Position.X)
Canvas.SetTop(di, di.Position.Y)
End Sub
End Class
And separate class:
Public Class DesignItem
Inherits ContentControl
Private _position As Point
Public Property Position() As Point
Get
Return _position
End Get
Set(ByVal value As Point)
_position = value
End Set
End Property
Private _size As Size
Public Property Size() As Size
Get
Return _size
End Get
Set(ByVal value As Size)
_size = value
End Set
End Property
End Class
Now this compiles and executes fine. However, if I change the xaml to this:
<paz:ZoomBorder x:Name="ZoomController">
On compile, I get:
Type Wpf.Controls.PanAndZoomBorder is not defined
My solution tree:
I did read another post about conflicting namespaces or something, but can't see how that relates to this scenario. A bizarre bug - does anyone have any ideas?
Related
I'm creating a UserControl in WPF, that is able to work for any object of type IMyNode. Basically, it receives an ObservableCollection through a dependency property, register to it and do some stuff.
In one of my usecase, I use in a control that uses(and need), an ObservableCollection of SomeSpecificNode. SomeSpecificNode is an implementation of IMyNode.
Currently, I've a binding error:
System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'System.Collections.ObjectModel.ObservableCollection`1[SomeSpecificNode]' and 'System.Collections.ObjectModel.ObservableCollection`1[IMyNode]'.
I understand why it happens, it doesn't know how to convert automatically an ObservableCollection<SomeSpecificNode> to ObservableCollection<IMyNode>.
What would be the correct approach to do this?
Using a converter would break the NotifyPropertyChange. Using a ObservableCollection<IMyNode> in my parent ViewModel would not work for the other control in the same page.
Thank you!
Here some pseudo code:
public class SomeSpecificNode: IMyNode{
}
public interface IMyNode{
}
public class ParentViewModel {
public ObservableCollection<SomeSpecificNode> SelectedNodes {get;}=> new ObservableCollection<SomeSpecificNode>()
}
<UserControl x:Class="ParentView"
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:ch.VibroMeter.Xms.Configurators.Controls.ActionBar"
xmlns:dxb="http://schemas.devexpress.com/winfx/2008/xaml/bars"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<ParentViewModel/>
</UserControl.DataContext>
<StackPanel Orientation="Horizontal" Name="RootContainer">
<SomeChildControl Nodes="{Binding SelectedNodes}" /><!-- This binding will fail !-->
</StackPanel
</UserControl>
public partial class ParentView : UserControl
{
public static readonly DependencyProperty NodesProperty = DependencyProperty.Register(
nameof(Nodes), typeof(ObservableCollection<IMyNode>), typeof(ParentView), new PropertyMetadata(default(ObservableCollection<IMyNode>), OnNodesChanged));
private static void OnNodesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//...
}
public ObservableCollection<IMyNode> Nodes
{
get { return (ObservableCollection<IMyNode>)GetValue(NodesProperty); }
set { SetValue(NodesProperty, value); }
}
}
You should change the type of the dependency property to a compatible type such as IEnumerable<IMyNode>.
You cannot set an ObservableCollection<IMyNode> property to anything else than an ObservableCollection<IMyNode> or null.
An ObservableCollection<SomeSpecificNode> is not an ObservableCollection<IMyNode> but it is an IEnumerable<IMyNode> assuming that SomeSpecificNode implements IMyNode.
So this compiles just fine;
IEnumerable<IMyNode> collection = new ObservableCollection<SomeSpecificNode>();
But this doesn't:
ObservableCollection<IMyNode> collection = new ObservableCollection<SomeSpecificNode>(); //Cannot implictly convert type...
The difference is that IEnumerable<T> is covariant. Please refer to the docs for more information.
In a User Control, I am trying to get a Command to modify a Property. I have an IncrementValueCommand and a Value property that I want to increment when a button is clicked. The button's Command is bound to the IncrementValueCommand and the Content is bound to the Value property.
I have tried two approaches to do this and in both cases the Button doesn't show the Value incrementing..
1st Approach: Dependency Property for Value
XAML:
<UserControl x:Class="UserControl1"
x:Name="root"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Content="{Binding Path=Value}"
Command="{Binding Path=IncrementValueCommand}" />
</UserControl>
Code behind:
Public Class UserControl1
Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(UserControl1), New PropertyMetadata(1))
Public Property IncrementValueCommand As ICommand
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
IncrementValueCommand = New RelayCommand(AddressOf IncrementValue)
End Sub
Public Property Value() As Integer
Get
Return GetValue(ValueProperty)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Private Sub IncrementValue()
Value += 1
End Sub
End Class
2nd approach: INotifyPropertyChanged for Value
XAML:
<UserControl x:Class="UserControl2"
x:Name="root"
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:WpfApp1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Content="{Binding Path=Value}"
Command="{Binding Path=IncrementValueCommand}" />
</UserControl>
Code behind:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class UserControl2
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _value As Integer = 1
Public Property IncrementValueCommand As ICommand
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
IncrementValueCommand = New RelayCommand(AddressOf IncrementValue)
End Sub
Public Property Value() As Integer
Get
Return _value
End Get
Set(value As Integer)
If _value <> value Then
_value = value
NotifyPropertyChanged()
End If
End Set
End Property
' 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.
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Sub IncrementValue()
Value += 1
End Sub
End Class
I left out the RelayCommand class which is a standard implementation for ICommand.
Any help will be greatly appreciated.
Working Code (thanks to Peter Duniho for his answer)
Adjust the code-behind constructor by creating the IncrementValueCommand first:
Public Sub New()
' Add any initialization after the InitializeComponent() call? Nah
IncrementValueCommand = New RelayCommand(AddressOf IncrementValue)
' This call is required by the designer.
InitializeComponent()
End Sub
As I've explained in this comment, the problem in this particular variant of your attempts to use a command to update a value is that you are initializing the IncrementValueCommand property after the call to InitializeComponent() in the class constructor.
The InitializeComponent() call is where the binding to that property is set up, i.e. Command="{Binding Path=IncrementValueCommand}" in your XAML. When that call is made, the property still has its default value of null.
When you assign the property a value later, because the property is an auto-implemented property, there's nothing about that assignment that would cause a property-change notification to happen, so the binding is never updated to reflect the new value.
You can either implement property-change notification for that property, just as is already done for the Value property, or you can (as I had suggested earlier) move the assignment within the constructor so that it occurs before the call to InitializeComponent instead of after.
I am following a tutorial on WPF data binding. I am trying to bind to a .NET object's property to a XAML control but the control does not display the expected data. These are what I believe to be the relevant sections of code:
In procedural code: (Note: removed ObservableCollection in PhotoGallery after original post)
Namespace PhotoGallery
Partial Public Class MainWindow
Inherits Window
Private photos As New Photos
...
End Class
Namespace PhotoGallery
Public Class Photos
Inherits Collection(Of Photo)
...
End Class
In XAML (Solution/Project name is Ch13-PhotoGallery):
<Window x:Class="PhotoGallery.MainWindow"
...
xmlns:local="clr-namespace:Ch13_PhotoGallery.PhotoGallery"
...>
<Window.Resources>
<local:Photos x:Key="Photos"/>
</Window.Resources>
And this is the control that is not displaying the data, which is the size of the Photos collection:
<Label x:Name="numItemsLabel" Background="AliceBlue" FontSize="8" Content="{Binding Source={StaticResource Photos}, Path=Count}"/>
When I typed in the < Label >, Intellisense popped up 'Count' for the Path property, so I think that tells me I have everything defined correctly.
If I add this line of procedural code behind to the refresh() method:
numItemsLabel.Content = photos.Count
Then the count is displayed correctly.
But I'm not getting the binding in XAML to display Photos.Count.
This creates a new instance of the Photos class:
<local:Photos x:Key="Photos"/>
If you want to bind to the Photos collection that you have created in your MainWindow.xaml.vb file you should expose it as a public property - you can only bind to properties but not fields - and set the DataContext of the window to an instance of the class where this property is defined, i.e. the window class itself in your case:
Class MainWindow
Public Property Photos As Photos
Public Sub New()
' This call is required by the designer.
InitializeComponent()
DataContext = Me
...
End Sub
End Class
You can the bind directly to the property:
<Label x:Name="numItemsLabel" Background="AliceBlue" FontSize="8" Content="{Binding Path=Photos.Count}"/>
Your ViewModel needs to implement the INotifyPropertyChanged interface, this will let your window listen to changes to your ViewModel
Here's an example of how to implement this interface in VB
https://social.msdn.microsoft.com/Forums/vstudio/en-US/7de44362-8b88-4292-b4ee-0385c3b34d7d/im-just-looking-for-a-simple-vb-net-mvvm-sample-wpf?forum=wpf
ViewModel
Public Class ViewModel
Implements INotifyPropertyChanged
Public Sub New()
Me.myTextValue = "default value..."
End Sub
Private myTextValue As String = String.Empty
Public Property MyTextProperty() As String
Get
Return Me.myTextValue
End Get
Set(ByVal value As String)
Me.myTextValue = value
NotifyPropertyChanged("MyTextProperty")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="{Binding MyTextProperty}"/>
</Grid>
</Window>
XAML Code Behind
Class MainWindow
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.DataContext = New ViewModel()
End Sub
End Class
I am attempting to handle an event in the application main form where said event is being raised from a user control populated on the main form. I've defined an Interface for the event which is being implemented in both the Main Form and the User Control. I'm using MvvmLight from GalaSoft for the MVVM support.
The main form code behind indicates that the event is attached, but when I do the check in the user control, it indicates that there is no handler attached to the event, so obviously, it won't ever get to the handler.
Any help would be appreciated.
The interface I've defined is pretty basic:
Public Interface IEventFiring
Event EventFiring(sender As Object)
End Interface
My Main Form Xaml looks like this:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EventFiringFromContolToMainForm"
xmlns:efc="clr-namespace:EventFiringControl;assembly=EventFiringControl"
Title="MainWindow">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding StatusText, Mode=OneWay}"
Height="25"
Margin="5"/>
<Separator Margin="5"/>
<efc:UserControl1/>
</StackPanel>
</Window>
The Code Behind is:
Imports EventFiringControl
Class MainWindow
Implements IEventFiring
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
AddHandler EventFiring, AddressOf EventFiringSub
If EventFiringEvent Is Nothing Then
CType(DataContext, MainWindowViewModel).StatusText = "Event did NOT Attach!!"
Else
CType(DataContext, MainWindowViewModel).StatusText = "Event Attached"
End If
End Sub
Public Event EventFiring(sender As Object) Implements IEventFiring.EventFiring
Private Sub EventFiringSub()
CType(DataContext, MainWindowViewModel).StatusText = "Event Fired"
End Sub
End Class
The view model for the Main Form is:
Imports GalaSoft.MvvmLight
Public Class MainWindowViewModel
Inherits ViewModelBase
Private _statusText As String
Public Property StatusText As String
Get
Return _statusText
End Get
Set(value As String)
_statusText = value
RaisePropertyChanged(Function() StatusText)
End Set
End Property
End Class
Now for the User Control.
Xaml file is:
<UserControl x:Class="UserControl1"
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:EventFiringControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<local:EventFiringViewModel/>
</UserControl.DataContext>
<Grid>
<Button Content="Cancel" Command="{Binding CancelCommand}" Height="50"/>
</Grid>
</UserControl>
The code behind is:
Public Class UserControl1
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
End Class
Finally, the View Model for the User Control is:
Imports GalaSoft.MvvmLight.Command
Imports GalaSoft.MvvmLight
Public Class EventFiringViewModel
Inherits ViewModelBase
Implements IEventFiring
Public ReadOnly Property CancelCommand As RelayCommand
Get
Return New RelayCommand(Sub() CancelSub())
End Get
End Property
Private Sub CancelSub()
If EventFiringEvent IsNot Nothing Then
RaiseEvent EventFiring(Me)
End If
End Sub
Public Event EventFiring(sender As Object) Implements IEventFiring.EventFiring
End Class
I'm currently learning some basics in WPF and I've been looking for the mistake for about 2 days. Hope you guys can help.
I'm trying to update my UI (in this case the content of a label) by using INotifyPropertyChanged and a binding in XAML. The thing is: it only takes the first value and puts it in the content. Furthermore nothing happens but the event (OnPropertyChanged) is fired.
This is what I have in XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:View x:Key="ViewModel"/>
</Window.Resources>
<Grid Margin="0,0,2,-4" DataContext="{Binding Source={StaticResource ViewModel}}">
....
<Label x:Name="lbl_money" Grid.ColumnSpan="2" Content="{Binding Path=PropMoney}" HorizontalAlignment="Left" Margin="403,42,0,0" VerticalAlignment="Top">
And this is the necessary part of my class View:
Public Class View
Inherits ViewModelBase
Private rest1 As New Restaurant
Private mainPlayer As New Player
Private mycurrentMoney As Double = 3
Private currentClickIncrease = mainPlayer.PropClickIncrease
Public Property PropMoney() As Double
Get
Return mycurrentMoney
End Get
Set(value As Double)
mycurrentMoney = value
OnPropertyChanged("mycurrentMoney")
End Set
End Property
Sub SelfClicked()
PropMoney() += 1
End Sub
Last but not least the MainWindow class, where i instantiate my view:
Class MainWindow
Private view As New View
Sub New()
InitializeComponent()
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
view.SelfClicked()
End Sub
End Class
So my mycurrentMoney is increasing each click and the event is fired but the label doesn't update.
Thank you in advance!
If you have Visual Studio 15 use NameOf operator instead of string literal like so:
NameOf(PropMoney);
If you later rename your property, it will still work opposed to string literal which will NOT. Alternatively modify your OnPropertyChange to make use of CallerMemberName
OnPropertyChange ([System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
}
The property name will be filled in, this works only in setter for current property however.
Also, set DataContext for whole window (Setting DataContext in XAML in WPF). DataContext={StaticResource ViewModel} and don't use Path in your Binding, just {Binding PropertyName}
Your OnPropertyChanged("mycurrentMoney") statement won't raise a property change on your property, because it's called PropMoney.
You have to set OnPropertyChanged("PropMoney") in your setter instead.
There are 2 problems with your code
First you raise PropertyChanged event for the backing field and should raise it for property name
OnPropertyChanged("PropMoney")
Second the property you change belong to different instance of View then the one set as DataContext. So in XAML remove DataContext changes, only leave property binding
<Window ...>
<Grid Margin="0,0,2,-4">
<!-- .... -->
<Label ... Content="{Binding Path=PropMoney}">
and then in code set DataContext of MainWindow to the instance that you create and modify
Class MainWindow
Private view As New View
Sub New()
InitializeComponent()
DataContext = view
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
view.SelfClicked()
End Sub
End Class