I am trying to store some data in XAML and loading it at runtime. For storing my xaml look like this .
<osp:OSPData x:Class="OptisetStore.Model.OSPData"
xmlns:osp="clr-namespace:OptisetStore.Model;assembly=OptisetStore"
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:OptisetStore.Model"
mc:Ignorable="d"
osp:OSPData.Name="OspCollection">
<osp:OSPData.Features>
<osp:Feature LocalName="feature1" x:Name="F1" IsEnabled="True" />
<osp:Feature LocalName="{Binding ElementName=F1, Path=LocalName, Mode=TwoWay}" IsEnabled="{Binding ElementName=F1, Path=IsEnabled, Mode=TwoWay}" />
</osp:OSPData.Features>
OSPData class
public partial class OSPData : DependencyObject
{
public string Name { get; set; }
public OSPData()
{
Features = new ObservableCollection<Feature>();
}
public ObservableCollection<Feature> Features
{
get { return (ObservableCollection<Feature>)GetValue(FeaturesProperty); }
set { SetValue(FeaturesProperty, value); }
}
// Using a DependencyProperty as the backing store for Features. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FeaturesProperty =
DependencyProperty.Register("Features", typeof(ObservableCollection<Feature>), typeof(OSPData), new PropertyMetadata(new ObservableCollection<Feature>()));
}
Feature Class:
public class Feature : DependencyObject
{
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
// Using a DependencyProperty as the backing store for IsEnabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.Register("IsEnabled", typeof(bool), typeof(Feature), new PropertyMetadata(false));
public string LocalName
{
get { return (string)GetValue(LocalNameProperty); }
set { SetValue(LocalNameProperty, value); }
}
// Using a DependencyProperty as the backing store for LocalName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LocalNameProperty =
DependencyProperty.Register("LocalName", typeof(string), typeof(Feature), new PropertyMetadata(""));
}
So after my ospdata is ready I store and at runtime I load the class to populate my UI. but Element name binding is not working.
StringReader stringReader = new StringReader(File.ReadAllText("Model/OSPData.xaml"));
XmlReader xmlReader = XmlReader.Create(stringReader);
var data = (OSPData)XamlReader.Load(xmlReader);
SimpleIoc.Default.GetInstance<TestingViewModel>().Data = data;
and my ui look like this :
<UniformGrid>
<ListBox ItemsSource="{Binding Data.Features}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding LocalName}" Width="100" />
<CheckBox Content="IsEnabled" IsChecked="{Binding IsEnabled}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UniformGrid>
I want the ability to edit OSPData.xaml and push it to application at runtime.
Related
I have the following DataTemplate
<DataTemplate x:Key="ArchiveModeContentTemplate">
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}"
Command="{Binding ElementName=factory,Path=BuildPopup}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="factory" Factory="{Binding ConfirmArchivingFactory}" />
</i:Interaction.Behaviors>
</Button>
</DataTemplate>
PopupFactory has a Command BuildPopup. this Command is given to the button with a binding with ElementName.
The first time this dataTemplate is displayed, it work fine. The button get the command. But if this dataTemplate is unloaded then displayed again, the binding give to the button the command of the previous instance of PopupFactory and not the newly created instance.
I pass in the constructor of PopupFactory and it is attached to the new button. So it is not a problem of PopupFactory being shared between templates.
Why this is happening? is it a bug with a the xaml cache?
Edit
I have an even stranger bug now.
I changed the syntax to the following to have the binding elementName after the name declaration in the Xaml. Now the command is working correctly but the the second button which is using a binding RelativeSource to find a command named GoBack don't work anymore. I used snoop to check the binding and it complain that it can't find the command BuildPopup. WPF is getting crazy!
<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}">
<i:Interaction.Behaviors>
<pop:PopupFactory x:Name="Archivefactory" Factory="{Binding ConfirmArchivingFactory}" IsSingleInstance="False" />
</i:Interaction.Behaviors>
<Button.Command>
<Binding ElementName="Archivefactory" Path="BuildPopup" />
</Button.Command>
</Button>
<Button Grid.Row="1" Grid.Column="1"
Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}"
Content="{StaticResource CrossIcon48}"
Foreground="Green"
ui:StyleProperties.Label="{DynamicResource Cancel}"
Command="{Binding Path=GoBack, RelativeSource={RelativeSource AncestorType={x:Type ui:DrillDown}}}" />
Edit
Here the code of PopupFactory
public class PopupFactory : Behavior<UIElement>
{
public ICommand BuildPopup { get; private set; }
private bool _canExecute;
private IDisposable _canexecuteSubscription = null;
public IObservable<bool> CanExecuteSource
{
get { return (IObservable<bool>)GetValue(CanExecuteSourceProperty); }
set { SetValue(CanExecuteSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for CanExecute. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CanExecuteSourceProperty =
DependencyProperty.Register("CanExecute", typeof(IObservable<bool>), typeof(PopupFactory), new PropertyMetadata(null));
private static void OnCanExecuteSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
factory._canexecuteSubscription?.Dispose();
if (arg.NewValue != null)
{
factory._canexecuteSubscription = ((IObservable<bool>)arg.NewValue)
.ObserveOnDispatcher()
.Subscribe(factory.UpdateCanExecute);
}
}
private void UpdateCanExecute(bool value)
{
_canExecute = value;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
}
public IFactory Factory
{
get { return (IFactory)GetValue(FactoryProperty); }
set { SetValue(FactoryProperty, value); }
}
// Using a DependencyProperty as the backing store for Factory. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FactoryProperty =
DependencyProperty.Register("Factory", typeof(IFactory), typeof(PopupFactory), new PropertyMetadata(null, OnFactoryChanged));
private static void OnFactoryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
{
var factory = obj as PopupFactory;
((RelayCommand<object>)factory.BuildPopup).RaiseCanExecuteChanged();
}
public UIElement PlacementTarget
{
get { return (UIElement)GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); }
}
// Using a DependencyProperty as the backing store for PlacementTarget. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementTargetProperty =
DependencyProperty.Register("PlacementTarget", typeof(UIElement), typeof(PopupFactory), new PropertyMetadata(null));
public PlacementMode Placement
{
get { return (PlacementMode)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
// Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(PopupFactory), new PropertyMetadata(PlacementMode.Center));
public bool IsSingleInstance
{
get { return (bool)GetValue(IsSingleInstanceProperty); }
set { SetValue(IsSingleInstanceProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSingleInsance. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSingleInstanceProperty =
DependencyProperty.Register("IsSingleInstance", typeof(bool), typeof(PopupFactory), new PropertyMetadata(false));
private bool _singleInstanceShowed = false;
public PopupFactory()
{
BuildPopup = new RelayCommand<object>((f) =>
{
ShowPopup(f);
}, (p) =>
{
return _canExecute && Factory != null && !_singleInstanceShowed;
});
UpdateCanExecute(true);
}
public IOverlayContainer ShowPopup(object parameter)
{
var param = new PopupParameter() { Owner = AssociatedObject };
UIElement target = PlacementTarget != null ? PlacementTarget : AssociatedObject;
var item = Factory.Build(parameter);
param.Content = item.Item;
param.Owner = AssociatedObject;
param.RemoveCondition = item.DisposeStream;
var container = OverlayManager.ShowPopup(param);
var placement = new PopupRelativePlacement(container as FrameworkElement, target,
Placement, false);
item.PostFactory?.Invoke();
if (IsSingleInstance)
{
_singleInstanceShowed = true;
OverlayManager.PopupOperations.Where((op) => op.Id == container.Id && op.Operationtype == OverlayOperation.OpType.PopupRemoved)
.Once((_) =>
{
_singleInstanceShowed = false;
((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
});
}
return container;
}
}
Problem solved.
I moved the PopupFactory Behavior to a visual parent of the button. This way, the behavior is created before the button and WPF don't mess up the name resolution during the binding.
Here is my code, I am writing a simple WPF application with Dependency property, I fail in the data binding for those sub property, but works in those normal property such as string, int. Any idea?
<!-- Work -->
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding address}"/>
<!-- Not work -->
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding child.Name}"/>
public class Child
{
public String Name { get; set; }
}
public Child child
{
get { return (Child)GetValue(childProperty); }
set { SetValue(childProperty, value); }
}
public static readonly DependencyProperty childProperty =
DependencyProperty.Register("child", typeof(Child), typeof(MainWindow), new PropertyMetadata(null));
public String address
{
get { return (String)GetValue(addressProperty); }
set { SetValue(addressProperty, value); }
}
public static readonly DependencyProperty addressProperty =
DependencyProperty.Register("address", typeof(String), typeof(MainWindow), new PropertyMetadata(null));
I have created a user control with collection property:
public static readonly DependencyProperty
MyListProperty = DependencyProperty.Register(
"MyList",
typeof(ObservableCollection<Test>),
typeof(UserControl1),
new FrameworkPropertyMetadata(new ObservableCollection<Test>())
);
public ObservableCollection<Test> MyList
{
get { return (ObservableCollection<Test>)base.GetValue(MyListProperty); }
set { base.SetValue(MyListProperty, value); }
}
public static readonly DependencyProperty
BProperty = DependencyProperty.Register(
"B",
typeof(string),
typeof(UserControl1),
new FrameworkPropertyMetadata(null)
);
public string B
{
get { return (string)base.GetValue(BProperty); }
set { base.SetValue(BProperty, value); }
}
The Test class is:
public class Test : DependencyObject
{
public static readonly DependencyProperty
AProperty = DependencyProperty.Register(
"A",
typeof(string),
typeof(Test),
new FrameworkPropertyMetadata(null)
);
public string A
{
get { return (string)base.GetValue(AProperty); }
set { base.SetValue(AProperty, value); }
}
}
Then, i'm trying to use my control for binding:
<TextBox x:Name="tb1" Text="def"/>
<my:UserControl1 x:Name="uc1" B="{Binding ElementName=tb1, Path=Text}">
<my:UserControl1.MyList>
<my:Test A="{Binding ElementName=tb1, Path=Text}"></my:Test>
<my:Test A="100"></my:Test>
</my:UserControl1.MyList>
</my:UserControl1>
The first binding (with B property of User Control) works correctly. The problem is with second binding (with A property of Test which is MyList element). When debugging i have two items in MyList but the A property of the first one is null. Please tell me what I am missing here?
The problem here is, that the Binding to ElementName=tb1 can not be resolved, even it will never be evaluated. A Binding to an ElementName is resolved for DependencyObjects which are in the visual or logical Tree of a WPF Application. Adding items to your ObservableCollection (MyList) only means adding the items to the Collection, but not into the Visual Tree.
Edit:
Here is the approach discussed in the comments:
In your Window/Page:
<Window.Resources>
<!-- Declare the ViewModel as Resource -->
<my:ViewModel x:Key="viewModel">
<my:ViewModel.MyList>
<my:Test A="Hello sweet" />
<my:Test A="ViewModel" />
</my:ViewModel.MyList>
</my:ViewModel>
</Window.Resources>
<!-- Assign Ressource as DataContext -->
<StackPanel DataContext="{StaticResource viewModel}">
<TextBox x:Name="tb1" Text="def"/>
<!-- Reference your list within the ViewModel -->
<ListBox ItemsSource="{Binding Path=MyList}">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Bind your property -->
<TextBlock Text="{Binding Path=A}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
And the implementation of ViewModel:
public class ViewModel
{
public ViewModel()
{
this.MyList = new ObservableCollection<Test>();
}
public ObservableCollection<Test> MyList { get; set; }
}
Of course, class Test no longer needs to implement a DependencyObject. Simple get/set Properties are okay.
I have a usercontrol which has a couple of textblocks on it
<UserControl x:Class="Tester.Messenger"
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"
x:Name="myUserControl"
>
<TextBlock Text="{Binding ElementName=myUserControl,Path=Header,Mode=TwoWay}" Foreground="LightGray" FontSize="11" Margin="3,3,0,-3"/>
<TextBlock Grid.Row="1" Grid.ColumnSpan="2" Text="{Binding ElementName=myUserControl,Path=Message, Mode=TwoWay}" Foreground="White" FontSize="16" Margin="3,-5"/>
In my code behind I have two dependency properties that I'm binding the above textblocks Text property to.
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("HeaderProperty", typeof(string), typeof(UserControl), new PropertyMetadata("header"));
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("MessageProperty", typeof(string), typeof(UserControl), new PropertyMetadata(null));
public string Header
{
get
{
return (string)GetValue(HeaderProperty);
}
set
{
SetValue(HeaderProperty, value);
}
}
public string Message
{
get
{
return (string)GetValue(MessageProperty);
}
set
{
SetValue(MessageProperty, value);
}
}
When I create a object of my UserControl and I change the Header and Message properties and place the control in an ItemControls items collection, then these aren't being reflected in the control. The control just displays the default values for the Header and Message.
Messenger m = new Messenger();
m.Header = "colin";
m.Message = "Download File ?";
iControl.Items.Add(m);
The first parameter in your call to DependencyProperty.Register is incorrect:
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("HeaderProperty", typeof(string), typeof(UserControl), new PropertyMetadata("header"));
should be:
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(UserControl), new PropertyMetadata("header"));
The string should be the name of the property as it will appear in XAML, that is without the "Property" suffix. Same goes for your message DependencyProperty.
I have a UserControl where I'd like to have some parameters of customization "radius"(double) and "contentSource" (string[]).
My UserControl is composed of a few nested controls:
<UserControl ...>
<Grid>
<my:Menu ...>
<my:Button>
</my:Button>
<my:Button>
</my:Button>
<my:Button>
</my:Button>
</my:Menu ...>
</Grid>
I'm trying to expose the parameters with:
public double Rad
{
get { return (double)GetValue(RadProperty); }
set { SetValue(RadProperty, value); }
}
public static readonly DependencyProperty RadProperty =
DependencyProperty.Register(
"Radius",
typeof(double),
typeof(Menu));
public String[] DataSource
{
get { return (String[])GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register(
"DataSource",
typeof(String[]),
typeof(Menu));
However, there seems to be two problems, the "string[]" parameter seems to be causing crashes, but mostly, I cannot set the "Radius" property at all. Is there something else I need to do to expose the parameter?
How are you trying to access the values? I have copied your code into a UserControl and it seems to work fine. Have you set the DataContext for the object you are accessing these values from?
Here is my test code, which might help:
public partial class uc : UserControl
{
public uc()
{
InitializeComponent();
this.DataContext = this;
this.DataSource = new string[] { "hello","There" };
this.Rad = 7;
}
public String[] DataSource
{
get { return (String[])GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register(
"DataSource",
typeof(String[]),
typeof(uc));
public double Rad
{
get { return (double)GetValue(RadProperty); }
set { SetValue(RadProperty, value); }
}
public static readonly DependencyProperty RadProperty =
DependencyProperty.Register(
"Radius",
typeof(double),
typeof(uc));
}
and the XAML:
<UserControl x:Class="WpfApplication18.uc"
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="300">
<Grid>
<StackPanel>
<TextBox Text="{Binding Path=DataSource[0]}"></TextBox>
<TextBox Text="{Binding Path=DataSource[1]}"></TextBox>
<TextBox Text="{Binding Path=Radius}"></TextBox>
<TextBox Text="{Binding Path=Radius}"></TextBox>
</StackPanel>
</Grid>
</UserControl>