This is the first time I have used Silverlight and as such, I am new to the whole xaml markup style. I am building a GIS application using a silverlight library provided by ESRI.
From my understanding, when the XAML page is parsed from top to bottom, the objects are created in the order. Is this correct? I have an esri Map object created on line 38 of my mainpage and then on line 247 of my mainpage, I create a DrawControl (a drawing control I made myself).
Part of how the objects in drawing tool works is that it is created by passing the map object to the constructor. With my Map object with name x:Name="Map", I have the following for my drawcontrol:
<local:DrawRootControl x:Name="DrawRoot" Height="152" Margin="216,10,0,0" Grid.Row="1" VerticalAlignment="Top" Visibility="Collapsed" map="{Binding ElementName=Map}"/>
Then in my control, I have this in the code behind:
public static readonly DependencyProperty mapProperty = DependencyProperty.Register
(
"map",
typeof(Map),
typeof(DrawRootControl),
null
);
public Map map
{
get { return (Map)GetValue(mapProperty); }
set { SetValue(mapProperty, value); }
}
..........
public DrawRootControl()
{
// Required to initialize variables
InitializeComponent();
MyDrawObject = new Draw(map)
{
LineSymbol = CanvasDraw.Resources["DrawLineSymbol"] as LineSymbol,
FillSymbol = CanvasDraw.Resources["DrawFillSymbol"] as FillSymbol
};
MyDrawObject.DrawComplete += MyDrawObject_DrawComplete;
}
When I am debugging, my map object in my constructor is null. I thought that if map is created earlier in the mainpage and then passed when I do that binding, it would not be null and would be initialized and created. Maybe I am doing binding incorrectly? I don't really understand the binding thing entirely.
Any help would be appreciated.
From my understanding, when the XAML page is parsed from top to bottom, the objects are created in the order. Is this correct?
yes top to bottom like html. Example:
<Grid x:Name="LayoutRoot" Background="White">
<Rectangle Fill="#FFE53400" Height="132" />
<Rectangle Fill="#FF0000E5" Height="132" Margin="0,51,0,0" />
</Grid>
Part of how the objects in drawing tool works is that it is created by passing the map object to the constructor.
If you are dependent to another UI element you will need to implement the callback to draw your control when the DependencyProperty has changed. In this example replace Title with Map
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(ComparisonReport), new PropertyMetadata(null, OnTitleChanged));
private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var o = d as ComparisonReport;
if (o != null && e.NewValue != null)
{
var n = ((ComparisonReport)d);
n.RadChart1.DefaultView.ChartArea.AxisX.Title = String.Format("{0} Comparison", e.NewValue);
}
}
If you have written a custom control you can wait till OnApplyTemplate() at which point you can locate the part (esri map object) by name. GetTemplateChild you can then attach to the esri events that affect your custom drawing.
Related
I am using SharpVector's SvgViewBox to show static resource images like this:
<svgc:SvgViewbox Source="/Resources/label.svg"/>
which works fine. However, I wish to control what image is shown through a binding to a view model.
The problem I'm experiencing is that the Source property of SvgViewbox is not bindable.
How can I get around this limitation without violating MVVM (e.g., passing the control into the view model and modifying it there)?
What you are looking for is called attached properties. MSDN offers a topic on it with the title "Custom Attached Properties"
In your case it may look as simple as this
namespace MyProject.Extensions
{
public class SvgViewboxAttachedProperties : DependencyObject
{
public static string GetSource(DependencyObject obj)
{
return (string) obj.GetValue(SourceProperty);
}
public static void SetSource(DependencyObject obj, string value)
{
obj.SetValue(SourceProperty, value);
}
private static void OnSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var svgControl = obj as SvgViewbox;
if (svgControl != null)
{
var path = (string)e.NewValue;
svgControl.Source = string.IsNullOrWhiteSpace(path) ? default(Uri) : new Uri(path);
}
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached("Source",
typeof (string), typeof (SvgViewboxAttachedProperties),
// default value: null
new PropertyMetadata(null, OnSourceChanged));
}
}
XAML to use it
<SvgViewbox Margin="0 200"
local:SvgViewboxAttachedProperties.Source="{Binding Path=ImagePath}" />
Note that local is the namespace prefix and it should point to your assembly/namespace where that class is located at, i.e. xmlns:local="clr-namespace:MyProject.Extensions;assembly=MyProject".
Then only use your attached property (local:Source) and never the Source property.
The new attached property local:Source is of type System.Uri. To update the image first assign null then the filename/filepath again.
I would like to disable column reording in a control we have created that is derived from ListView. This control is called a SortableListView. I thought a dependency property would be the best way to implement this, but the ((SortableListVIew)source).View is returning null. Here is the code:
public class SortableListView : ListView
{
// ...lots of other properties here
public static readonly DependencyProperty AllowsColumnReorderProperty =
DependencyProperty.Register(
"AllowsColumnReorder",
typeof(bool),
typeof(SortableListView),
new UIPropertyMetadata(true, AllowsColumnReorderPropertyChanged));
public bool AllowsColumnReorder
{
get
{
return (bool)this.GetValue(AllowsColumnReorderProperty);
}
set
{
this.SetValue(AllowsColumnReorderProperty, value);
}
}
private static void AllowsColumnReorderPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
ViewBase vb = ((SortableListView)source).View;
if (vb != null)
{
((GridView)vb).AllowsColumnReorder = (bool)e.NewValue;
}
}
And the XAML:
<TableControls:SortableListView x:Name="QueueViewTable" Margin="0,0,0,0"
Style="{StaticResource ListViewStyle}"
ItemsSource="{Binding Path=QueueList}"
ItemContainerStyle="{StaticResource alternatingListViewItemStyle}"
AlternationCount="2"
SelectionMode="Single"
SortEnabled="False"
AllowsColumnReorder="false">
The trouble is that vb is always null so the method fails to set AllowsColumnReoder. I am quite sure that the cast is valid because the code originally looked like this in OnInitialized:
((GridView)this.View).AllowsColumnReorder = false;
...but I need to set the AllowsColumnReorder on a particular instance of the view so this code is no good.
Can anyone tell me what I'm doing wrong? Or is there a better way to set this property?
The View property of ListView is itself a dependency property that could change. It appears to not be set yet when you're setting your property?
You may have to override the View property in your sortable list view, so you can add a property change listener, and then apply your sort property when the view itself gets set?
in wpf, you can override a dependency property declared in a parent class, shown here: http://msdn.microsoft.com/en-us/library/ms754209.aspx
you'd override the metadata for the View property, and in the PropertyMetadata param you set there, you can pass a function like you are above for AllowsColumnReorderPropertyChanged
in that handler, you'd check to see if the new view is a gridview, and then set your property.
that way, the either order of AllowsColumnReorder or View getting set will properly set your property.
I have a control in which I need to set data template based on various conditions so I decided to use a DataTemplateSelector which selects templates from resources of the control its being assigned to.
This works, but here is a catch: I am reloading these resources from file (when there is file system change) and I need to update already rendered controls with the new template. This would work if I simply used DynamicResource instead of selector.
Selector looks something like this:
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
//complex rules that select the template are here
//this unfortunately sets the template statically - if it changes, it won't get updated
return template;
}
So if the resources change, the selector is never reevaluated as it would be if I used DynamicResource.
I had an idea to solve this: select the template in ViewModel, so that when resources change, I can update my DataTemplate property.
My attempt of ViewModel (simplified example, it implements INotifyPropertyChange properly):
class MyViewModel {
public DataTemplate DataTemplate {get;set;}
public MyModel Model {
get {return _model;}
set {
if(_model != value) {
_model = value;
//Select new template here
//DUH: how do I access the resources as I would in DataTemplateSelector, when I don't have access to the container parameter?
}
}
}
}
I am pretty sure that I am doing this the wrong way, but how to do it properly? I don't want to access the resources from some hard-coded static location for various reasons. I really need to find them in the container it is being assigned to.
I know the question is confusing, so feel free to ask and I will try to clarify.
So after long hours of trying to figure this out using various hackish methods, it showed to be really easily solvable problem.
We set our data template (only key to data template in fact) in view-model and then apply the template in simple attached property.
xaml:
<ContentControl Content="{Binding Content}" local:ContentTemplate.ContentTemplateKey="{Binding TemplateKey}">
<!-- Some other stuff -->
</ContentControl>
attached property:
public static class ContentTemplate
{
public static object GetContentTemplateKey(DependencyObject obj)
{
return (object)obj.GetValue(ContentTemplateKeyProperty);
}
public static void SetContentTemplateKey(DependencyObject obj, object value)
{
obj.SetValue(ContentTemplateKeyProperty, value);
}
public static readonly DependencyProperty ContentTemplateKeyProperty = DependencyProperty.RegisterAttached("ContentTemplateKey", typeof(object), typeof(ContentTemplate), new UIPropertyMetadata(null, OnContentTemplateKeyChanged));
private static void OnContentTemplateKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var key = e.NewValue;
var element = d as FrameworkElement;
if (element == null)
return;
element.SetResourceReference(ContentControl.ContentTemplateProperty, key);
}
}
binding object if resource uses x:Key="ResourceName":
new
{
Content = something,
TemplateKey = "ResourceName",
}
binding object if resource uses TargetType="{x:Type Person}":
new
{
Content = something,
TemplateKey = new DataTemplateKey(typeof(Person)),
}
Of course the binding object should implement INotifyPropertyChange so the templates update on the fly.
I'm trying to create a GUI (WPF) Library where each (custom) control basically wraps an internal (third party) control. Then, I'm manually exposing each property (not all of them, but almost). In XAML the resulting control is pretty straightforward:
<my:CustomButton Content="ClickMe" />
And the code behind is quite simple as well:
public class CustomButton : Control
{
private MyThirdPartyButton _button = null;
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public CustomButton()
{
_button = new MyThirdPartyButton();
this.AddVisualChild(_button);
}
protected override int VisualChildrenCount
{
get
{ return _button == null ? 0 : 1; }
}
protected override Visual GetVisualChild(int index)
{
if (_button == null)
{
throw new ArgumentOutOfRangeException();
}
return _button;
}
#region Property: Content
public Object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
"Content", typeof(Object),
typeof(CustomButton),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeContent))
);
private static void ChangeContent(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
(source as CustomButton).UpdateContent(e.NewValue);
}
private void UpdateContent(Object sel)
{
_button.Content = sel;
}
#endregion
}
The problem comes after we expose MyThirdPartyButton as a property (in case we don't expose something, we would like to give the programmer the means to use it directly). By simply creating the property, like this:
public MyThirdPartyButton InternalControl
{
get { return _button; }
set
{
if (_button != value)
{
this.RemoveVisualChild(_button);
_button = value;
this.AddVisualChild(_button);
}
}
}
The resulting XAML would be this:
<my:CustomButton>
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
And what I'm looking for, is something like this:
<my:CustomButton>
<my:CustomButton.InternalControl Content="ClickMe" />
But (with the code I have) its impossible to add attributes to InternalControl...
Any ideas/suggestions?
Thanks a lot,
--
Robert
WPF's animation system has the ability to set sub-properties of objects, but the XAML parser does not.
Two workarounds:
In the InternalControl property setter, take the value passed in and iterate through its DependencyProperties copying them to your actual InternalControl.
Use a build event to programmatically create attached properties for all internal control properties.
I'll explain each of these in turn.
Setting properties using the property setter
This solution will not result in the simplified syntax you desire, but it is simple to implement and will probably solve the main problem with is, how to merge values set on your container control with values set on the internal control.
For this solution you continue to use the XAML you didn't like:
<my:CustomButton Something="Abc">
<my:CustomButton.InternalControl>
<thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>
but you don't actually end up replacing your InternalControl.
To do this, your InternalControl's setter would be:
public InternalControl InternalControl
{
get { return _internalControl; }
set
{
var enumerator = value.GetLocalValueEnumerator();
while(enumerator.MoveNext())
{
var entry = enumerator.Current as LocalValueEntry;
_internalControl.SetValue(entry.Property, entry.Value);
}
}
}
You may need some additional logic to exclude DPs not publically visible or that are set by default. This can actually be handled easily by creating a dummy object in the static constructor and making a list of DPs that have local values by default.
Using a build event to create attached properties
This solution allows you to write very pretty XAML:
<my:CustomButton Something="Abc"
my:ThirdPartyButtonProperty.Content="ClickMe" />
The implementation is to automatically create the ThirdPartyButtonProperty class in a build event. The build event will use CodeDOM to construct attached properties for each property declared in ThirdPartyButton that isn't already mirrored in CustomButton. In each case, the PropertyChangedCallback for the attached property will copy the value into the corresponding property of InternalControl:
public class ThirdPartyButtonProperty
{
public static object GetContent(...
public static void SetContent(...
public static readonly DependencyProperty ContentProperty = DependencyProperty.RegisterAttached("Content", typeof(object), typeof(ThirdPartyButtonProperty), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((CustomButton)obj).InternalControl.Content = (object)e.NewValue;
}
});
}
This part of the implementation is straightforward: The tricky part is creating the MSBuild task, referencing it from your .csproj, and sequencing it so that it runs after the precompile of my:CustomButton so it can see what additional properties it needs to add.
I've created a UserControl which is essentially a button. It's got an Image and a Label on it and I've created two properties to set the Image's source and the Label's text like so:
public ImageSource Icon
{
get { return (ImageSource)this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); icon.Source = value; }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(NavigationButton));
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); label.Content = value; }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(NavigationButton));
However, when I've added the control to my Page, the controls wont respond to any properties I set in XAML, e.g. <controls:MusicButton Icon="/SuCo;component/Resources/settings.png/> does nothing.
What am I doing wrong?
CLR properties that wrap dependency properties should never have any logic other than calling GetValue and SetValue. That is because they may not even be called. For example, the XAML compiler will optimize by calling GetValue/SetValue directly rather than using your CLR property.
If you need to execute some logic when a dependency property is changed, use metadata:
public ImageSource Icon
{
get { return (ImageSource)this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(NavigationButton), new FrameworkPropertyMetadata(OnIconChanged));
private static void OnIconChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
//do whatever you want here - the first parameter is your DependencyObject
}
EDIT
In my first answer, I assumed your control's XAML (be it from a template or directly in a UserControl) is correctly hooked up to the properties. You haven't showed us that XAML, so it was perhaps an incorrect assumption. I'd expect to see something like:
<StackPanel>
<Image Source="{Binding Icon}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
And - importantly - your DataContext must be set to the control itself. You can do this in various different ways, but here is a very simple example of setting it from the code behind:
public YourControl()
{
InitializeComponent();
//bindings without an explicit source will look at their DataContext, which is this control
DataContext = this;
}
Have you tried setting the text property as well? The source of an image may just be wrong. Text is much more straight forward.
Also, in your example, you missed a quotation mark. So if it's copied from your real code, you may want to check that.
Barring those minor admittedly unlikely causes for your problem, I'd suggest setting the properties in code to check whether that has any effect. If it has, then you should really check your XAML.
Since you haven't posted the rest of your code, I can't really tell if you have problems somewhere else that might affect the control.
And yes, I know I'm not very helpful, but I've been working with WPF for only a little while. Hope it helps anyway.