I'm making a tool where I can edit images, so I have a custom Control to draw on a canvas. For this control I bound the resolution and the color of the brush which work perfectly fine, but when I try to bind a Canvas I always get null in myViewModel.
I tried binding a List instead, but that didn't work either. I've been trying to figure out what the problem is for about 12 hours now so I think it's time to ask you guys for help so I can maybe figure out what's wrong.
I used to work with singletons for my viewmodels, and then I could link to the Canvas just fine, but then I realised this is a bad way of working so I tried to bind it with dependency properties.
Whenever I try to access the Canvas it has a value of null, even though I initialized it.
Control
public static readonly DependencyProperty CanvasProperty = DependencyProperty.Register(
"CanvasToDraw", typeof(Canvas), typeof(DrawCanvas), new PropertyMetadata(default(Canvas), null, null));
public Canvas CanvasToDraw
{
get { return (Canvas)GetValue(CanvasProperty); }
set { SetValue(CanvasProperty, value); }
}
public DrawCanvas()
{
InitializeComponent();
CanvasToDraw = CanvasGrid;
}
XAML
<Controls:DrawCanvas x:Name="DrawCanvas1" Resolution="{Binding Resolution}" CanvasToDraw="{Binding DrawingCanvas}" RectangleList="{Binding RectangleList, ElementName=DrawCanvas1}" ColorToDraw="{Binding SelectedColor}" HorizontalAlignment="Left" Height="256" Margin="10,40,0,0" VerticalAlignment="Top" Width="256" />
VIEWMODEL
private Canvas _drawingCanvas;
public Canvas DrawingCanvas
{
get { return _drawingCanvas; }
set
{
_drawingCanvas = value;
OnPropertyChanged("DrawingCanvas");
}
}
I can provide more code if needed, but this is most of the relevant code. And yes I do use INotifyPropertyChanged, since I have other things that I bound that do work.
I've been looking for a solution for so long, but the answer was so simple. I assumed the binding was two way, but appareantly I had to set Mode=TwoWay and everything worked like a charm. (Linking an observeablecollection, not a Canvas)
Related
I've got a listview that has, as a datatemplate, a relatively complex UserControl, with a couple properties that are being binded to it.
The ItemsPanel is the following:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel VerticalAlignment="Bottom" ItemsUpdatingScrollMode="KeepLastItemInView"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
My listview generally contains about 70 items, however I'm running into quite a major issue:
Every time I scroll up about 30 items and then go back down, the properties seem to have gotten messed up, and some of them seem to have switched with the properties of items higher up.
For example, if before I had the following:
property A = 1
property A = 2
property A = 3
After scrolling up and back down I would have:
property A = 1
property A = 2
property A = 1
How can I ensure that the items either keep their properties or reload them correctly?
I finally figured out how to fix this: If anyone else is running into a similar issue, it's potentially because you're handling the PropertyChanged event without resetting properties that are missing or are null. For example, if this is the usercontrol inside the datatemplate of a listview:
<local:CustomControl CustomText={Binding text}/>
And it is being handled like this:
public string CustomText
{
get { return (string)GetValue(CustomTextProperty); }
set { SetValue(CustomTextProperty, value); }
}
public static readonly DependencyProperty CustomTextProperty = DependencyProperty.Register(
nameof(CustomText),
typeof(string),
typeof(CustomControl),
new PropertyMetadata(string.Empty, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as CustomControl;
if(instance.CustomText != "")
myTextBlock.Text = instance.CustomText;
}
Every time that virtualization recycles a CustomControl, if ever it is being binded to an empty string, because of the if(instance.CustomText != "") the textblock will not get updated, and old properties will show up again. The same will happen if OnPropertyChanged never gets called for some reason.
It seems kind of stupid in this particular situation, but it can get quite confusing on large usercontrols with a lot of properties and subproperties
Can anyone explain how XAML data binding expressions are evaluated? I have a control with a registered property, VisualState.
public CardStates VisualState
{
get
{
return (CardStates)this.GetValue(VisualStateProperty);
}
set
{
this.SetValue(VisualStateProperty, value);
}
}
public static readonly DependencyProperty VisualStateProperty = DependencyProperty.RegisterAttached("VisualStateProperty", typeof(CardStates), typeof(StateManager), new PropertyMetadata(null, (s, e) => { }));
In the xaml I attempt to bind a value to this property. State exists parent's DataContext object.
<local:CardControl VisualState="{Binding State.Value}" />
The generated code in XamlTypeInfo.g.cs looks like this
private void set_4_CardControl_VisualState(object instance, object Value)
{
var that = (global::MeetEric.UI.Controls.CardControl)instance;
that.VisualState = (global::MeetEric.ViewModels.CardStates)Value;
}
This code throws an InvalidCastException because the value of Value is a Windows.UI.Xaml.Data.Binding object.
Am I missing something obvious to enable working with data bindings? Do I need some form of converter?
Try to change typeof(StateManager) to typeof(CardControl). (at DependencyProperty.RegisterAttached)
This argument requires the owner of the DependencyProperty.
This error occurs when you try to using Binding with a regular property. In order for the binding to resolve to the right type, you need to be binding to a DependencyProperty.
As others have mentioned, your syntax is a bit off which is probably causing the problem here.
This error is certainly very cryptic. I just hit it in my own project, but as soon as I changed my property to a dependency one, it worked perfect.
EXPLANATION:
I can't use code behind because I'm using some interfaces who hare injected with Ninject, I can't just do service = new service() because there are a lot more dependencies behind that one. As for the question about the type name as a converter parameter, I found this solution best for binding my model to my UI. I use this for all of my modules and is working great. Just my Map control is not working at all.
Atm I'm trying to include a Microsoft.Phone.Maps.Controls.Map control in my Windows Phone 8 app. But I can't seem to get it working.
Because of Dependency injection and stuff I can't use the code behind of my xaml page. So I made myself a MapController who is linked to the Xaml as a DataContext. But I got some problems with it.
XAML:
<phone:PhoneApplicationPage
...
xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
DataContext="{Binding Source={StaticResource DynamicLocator}, Converter={StaticResource IndexConverter}, ConverterParameter='BaseCpr.Plugins.ShopLocator.Model.MapController,BaseCpr.Plugins'}">
<Grid>
<maps:Map DataContext="{Binding MapControl, Mode=TwoWay}">
</maps:Map>
</Grid>
</phone:PhoneApplicationPage>
MapController:
public MapController(IService Service) {
//Service is used here a lot
MapControl = new Map() {
CartographicMode = MapCartographicMode.Road,
Heading = 0,
Pitch = 0,
ZoomLevel = 7,
Center = new GeoCoordinate(50.50, 4.000)
};
}
private Map mapControl;
public Map MapControl {
get { return mapControl; }
set {
if (mapControl != value) {
mapControl = value;
RaisePropertyChanged(() => MapControl);
}
}
}
The link between the XAML and my MapController is working (tested it with simple boolean bindings). And when I'm debugging the getter of MapControl get called. But my map on my screen is still at the default view. Can someone help me out what I'm doing wrong?
Thanks
EDIT 2: (some more info)
In my MapController I have the following function as well:
public async void getPhoneLocation() {
try
{
//getting location and put it in geoposition
var cord = new GeoCoordinate(geoposition.Coordinate.Latitude, geoposition.Coordinate.Longitude);
MapControl.SetView(cord, 16, 0, 0, MapAnimationKind.Parabolic);
CreateAndAddUserMarker(cord);
}
catch (Exception ex)
{
//TODO
}
}
This is why I can't just bind all Map properties of my XAML map control. I need to call the SetView() function as well. I also need to add Layers to my map to add pins on the map. getPhoneLocation() is called in the MapController constructor.
To solve my problem by making a MapBehavior and bind a ViewModel property to a Dependency Property from the MapBehavior.
That way, when my Location get's updated it will fire the Dependency Property PropertyChangedCallback.
public DependencyProperty LocationProperty = DependencyProperty.Register(
"Location", typeof(GeoCoordinate), typeof(MapBehavior), new PropertyMetadata(null, (sender, args) =>
{
if (args.NewValue != null && args.NewValue != args.OldValue)
{
var sendMap = ((MapBehavior)sender);
sendMap.AssociatedObject.SetView((GeoCoordinate)args.NewValue, 14, 0, 0, MapAnimationKind.Parabolic);
}
}));
In sender is the Map control, that way the SetView() method on your MapControl can be used :)
It took me a while to get this solution so I wanted to share it anyway
When should I be building Inlines in a TextBlock? I have a TextBlock-derived class that, when given text in a certain field, call it MyText, converts the text into a set of inlines when MyText has changed.
Whenever MyText changes, I clear the Inlines and build them, colorizing each word as needed. For this example, consider:
private void MyTextBlock_MyTextChanged(object sender, EventArgs e)
{
Inlines.Clear();
if (!string.IsNullOrEmpty(this.MyText))
{
var run = new Run();
run.Foreground = Brushes.DarkRed;
run.Text = this.MyText;
Inlines.Add(run);
}
}
This has worked very well. However, recently we placed the Control into a DataGrid, and some strange things have started happening. Apparently the DataGrid swaps out the context and for the most part this works. However, when we add or delete data from the DataGrid ItemsSource, something goes awry, and TextChanged doesn't seem like it is called (or at least not called at the same time). MyText can be one value, and the Inlines either blank or a different value.
I think that the place to build the Inlines is NOT during MyTextChanged, but maybe when the rendering of the Control starts. I've also tried when the DataContextChanged, but this does not help.
In my constructor, I have
this.myTextDescriptor = DependencyPropertyDescriptor.FromProperty(
MyTextProperty, typeof(MyTextBlock));
if (this.myTextDescriptor != null)
{
this.myTextDescriptor.AddValueChanged(this, this.MyTextBlock_MyTextChanged);
}
corresponding to a dependency property I have in the class
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(MyTextBlock));
private readonly DependencyPropertyDescriptor myTextDescriptor;
Update: If it is any kind of clue, the problem DataGrid cells seem to be the ones that are off-screen when the addition or removal happens. I also tried OnApplyTemplate, but that didn't help.
Update2: Perhaps a better solution might be to create bindable inlines?
DataGrids virtualize their content, so if a row is not visible it will not be loaded. That being the case, have you tried also rebuilding the inlines when the Loaded event fires?
enter code hereI have a ScrollViewer in Silverlight that is not scrolling vertically whenever I call the ScrollToVerticalOffset method from the code behind.
Basically, I have my View (UserControl) that contains the ScrollViewer. I invoke an action from my ViewModel that triggers an event in the View's code-behind that sets the VerticalOffset to a specific value.
First of all, I know this is very ugly. Ideally I wish that I could have an attachable property that I could bind to a property in my ViewModel, that, when set, would cause the VerticalOffset property (which I know is read-only) to be updated, and the ScrollViewer to scroll.
The ScrollViewer contains dynamic content. So, if the user is viewing content in the ScrollViewer, and scrolls half-way down, and then clicks on a button, new content is loaded into the ScrollViewer. The problem is that the ScrollViewer's vertical offset doesn't get reset, so the user has to scroll up to read the content. So, my solution was to be able to control the vertical offset from the ViewModel, and I have racked my brain and can't come up with a viable solution, so I am looking for someone to help, please.
By the way - I have included code from a class I put together for an attachable property. This property binds to a property in my ViewModel. When I set the property in the ViewModel, it correctly triggers the PropertyChanged callback method in this class, which then calls the ScrollToVerticalOffset method for the ScrollViewer, but the ScrollViewer still doesn't scroll.
public class ScrollViewerHelper
{
public static readonly DependencyProperty BindableOffsetProperty =
DependencyProperty.RegisterAttached("BindableOffset", typeof(double), typeof(ScrollViewerHelper),
new PropertyMetadata(OnBindableOffsetChanged));
public static double GetBindableOffset(DependencyObject d)
{
return (double)d.GetValue(BindableOffsetProperty);
}
public static void SetBindableOffset(DependencyObject d, double value)
{
d.SetValue(BindableOffsetProperty, value);
}
private static void OnBindableOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ScrollViewer scrollViewer = d as ScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}
}
}
This approach is a little bit funky in my opinion, as I think of both a ScrollViewer and a VerticalScrollOffset as "View" entities that should have very little (or nothing) to do with a ViewModel. It seems like this might be forcing MVVM a little too much, and creating a lot of extra work in creating an attached dependency property and basically trying to keep a bound Offset ViewModel property in sync with the readonly VerticalScrollOffset of the ScrollViewer.
I am not exactly sure of what you are trying to achieve, but it sounds like you are trying to scroll to a specified offset when some dynamic element is added to the underlying panel of your ScrollViewer. Personally, I would just want to handle this behavior with a little bit of code in my View and forget about tying it to the ViewModel.
One really nice way to do this type of thing in Silverlight 3 is with Blend behaviors. You write a little bit of behavior code in C# and then can attach it declaratively to an element in XAML. This keeps it reusable and out of your code-behind. Your project will have to reference the System.Windows.Interactivity DLL which is part of the Blend SKD.
Here's a simple example of a simple Blend behavior you could add to a ScrollViewer which scrolls to a specified offset whenever the size of the underlying content of the ScrollViewer changes:
public class ScrollToOffsetBehavior : Behavior<ScrollViewer>
{
private FrameworkElement contentElement = null;
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register(
"Offset",
typeof(double),
typeof(ScrollToOffsetBehavior),
new PropertyMetadata(0.0));
public double Offset
{
get { return (double)GetValue(OffsetProperty); }
set { SetValue(OffsetProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.contentElement != null)
{
this.contentElement.SizeChanged -= contentElement_SizeChanged;
}
if (this.AssociatedObject != null)
{
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
}
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
this.contentElement = this.AssociatedObject.Content as FrameworkElement;
if (this.contentElement != null)
{
this.contentElement.SizeChanged += new SizeChangedEventHandler(contentElement_SizeChanged);
}
}
void contentElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.AssociatedObject.ScrollToVerticalOffset(this.Offset);
}
}
You could then apply this behavior to the ScrollViewer in XAML (and specify an offset of 0 to scroll back to the top):
<ScrollViewer>
<i:Interaction.Behaviors>
<local:ScrollToOffsetBehavior Offset="0"/>
</i:Interaction.Behaviors>
...Scroll Viewer Content...
</ScrollViewer>
This would be assuming that you always want to scroll to the offset whenever the content size changes. This may not be exactly what you are looking for, but it is one example of how something like this can be done in the view using a behavior.