Use Constants in XAML WPF C# occur MarkupExtensions error - c#

I have in a project called Common and there I have a Constants class:
public static class Constants
{
public class ListViewContextMenu
{
public const string ADD = "addToolStripMenuItem";
public const string RENAME = "renameToolStripMenuItem";
public const string DISABLE = "disableToolStripMenuItem";
public const string DELETE = "deleteToolStripMenuItem";
public const string ADD_TEXT = "Add";
public const string RENAME_TEXT = "Rename";
public const string DISABLE_TEXT = "Disable";
public const string ENABLE_TEXT = "Enable";
}
}
In other project, where is WPF, I want to use above constant in context menu header:
xmlns:constants="clr-namespace:Common;assembly=Common"
and
<ListView HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="0" Name="listView1" SelectionMode="Single" ItemsSource="{Binding TrackerList,Mode=TwoWay}" DisplayMemberPath="Title">
<ListView.ContextMenu>
<ContextMenu Name="lv_ctx">
<MenuItem Header="Add" Name="{x:Static constants:Constants+ListViewContextMenu.ADD}"></MenuItem>
<Separator/>
<MenuItem Header="{x:Static constants:Constants+ListViewContextMenu.RENAME_TEXT}" Name="{x:Static constants:Constants+ListViewContextMenu.RENAME}"></MenuItem>
<MenuItem Header="{x:Static constants:Constants+ListViewContextMenu.DELETE_TEXT}" Name="{x:Static constants:Constants+ListViewContextMenu.DELETE}"></MenuItem>
<MenuItem Header="{x:Static constants:Constants+ListViewContextMenu.DISABLE_TEXT}" Name="{x:Static constants:Constants+ListViewContextMenu.DISABLE}"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
But I get error:
MarkupExtensions are not allowed for Uid or Name property values, so
'{x:Static constants:Constants+ListViewContextMenu.ADD}' is not valid.
I expected to work since those are constants...

It is not a good idea to bind the Name property of an object (even if you are using Static Markup extension). Two reasons come to my mind: FindName() ContextMenu method won't work and you cannot refer the controls from your code-behind if their names are dynamic (i.e. they can change at run time), because the name is not known at building time.
I guess the exception that is thrown is meant to prevent those issues.
If you need to use those constants in your MenuItems, you can consider using Tag property. I hope it can help you.

x:Static is a markup extension.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/markup-extensions-and-wpf-xaml
I think it doesn't matter what x:Static is returning. The parser is checking for any markup extension and raising that error.
Whoever designed the parser decided that it was a good idea if the unique identifier for a ui object was right there in xaml as a string.
I can't see why you'd want to abstract Name in this way.
If the Name is set there in xaml then you can see it as you read the xaml. If your approach worked then you would need to go find the value in some other class.
All that abstracting seems to be achieving is making it harder to read the xaml. I see that as a negative, myself.
I think it's also worth explaining best practice.
Meaning MVVM for the vast majority of wpf dev teams.
A much more usual way of working would be to bind a collection of viewmodels to the itemssource of a menu. They would expose icommand and description bound to command and Header of the menuitem. No need to know the name of any menuitem clicked because it invokes the bound icommand.

Related

XAML binding to a codebehind class property collection is blank (WPF)

My codebehind defines a simple class with properties and a constructor as so:
public class Question
{
public string[] Answers
{
get; set;
}
public int CorrectAnswerIndex
{
get; set;
}
public Question(string[] answers, int correctIndex)
{
this.Answers = answers;
this.CorrectAnswerIndex = correctIndex;
}
}
There then exists a public object of that type that gets initialised in the window's constructor like so:
CurrentQuestion = new Question(
new string[] { "First", "Second", "Third", "Fourth" }, 2
);
I then have the following XAML in an attempt to print out all of the possible answers from said question.
<Grid Margin="150,150,150,150" DataContext="local:CurrentQuestion">
<ListBox ItemsSource="{Binding Answers}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
The local namespace is defined previously as the CLR namespace.
However, my list emerges entirely empty. There are no binding errors at runtime.
What's going on here? It seems a simple example that just won't run. I feel I've missed something "obvious."
This will look at ListBox.DataContext for a property named Answers, and try to use that for the ItemsSource.
<ListBox ItemsSource="{Binding Answers}">
ListBox.DataContext will be inherited from the parent Grid. Unfortunately, the grid's DataContext is a string, and strings don't have a property called Answers. So the Binding can't do anything and gives you null.
<Grid Margin="150,150,150,150" DataContext="local:CurrentQuestion">
<ListBox ItemsSource="{Binding Answers}">
XAML implicit conversions are a Do-What-I-Mean thing, thus a source of much confusion. There are times when you can put local:CurrentQuestion in an attribute value and have it be taken as a data type -- but this is not one of those times. And a data type isn't what you meant to provide anyway. You wanted a property by that name. But local: is a namespace, a literal CLR namespace like System.Windows.Controls, not a reference to an object.
Here's how the XAML in a UserControl can bind to a property of the UserControl. If it's a Window, change UserControl to Window.
<Grid Margin="150,150,150,150">
<ListBox
ItemsSource="{Binding CurrentQuestion.Answers, RelativeSource={RelativeSource AncestorType=UserControl}}">
I'm just guessing that CurrentQuestion is a property of the UserControl. Let me know if it's somewhere else.
You're also probably going to run into problems when you update CurrentQuestion, unless it's a dependency property. If it's a plain old CLR property like this, the UI won't be notified when its value changes:
public Question CurrentQuestion { get; set; }

XAML reference data context

I am using PRISM to auto-wire my Views & ViewModels, however I have encountered a problem I cannot solve.
I am using a calendar control, which enables users to create new appointments via opening new modal window & saving it to calendar.
This window, is styled via a ControlTemplate, where I have the following item:
<telerik:RadComboBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="3"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.EmployeeList}">
Items Source of this combobox is the ViewModels DataContext.EmployeeList - ObservableCollection<Employee>.
This would work like a charm as long as it would not be a new pop-out window. That way, I believe it is a userControl as well and therefore my regular code does not recognize any EmployeeList.
There might be 2 ways how to solve it (I don't have direct access to the modal window as it is being automatically generated by the control itself - I am using Telerik suite).
1) Make sure that the ItemsSource will dig deeper than the very first UserControl that it finds. Maybe by slightly changing the code, it will be able to do so? (Maybe using something like AncestorLevel...?).
2) Telerik has shown an example of how to achieve that by the following line:
<local:ViewModel x:Key="ViewModel" /> -- define key first
ItemsSource="{Binding Source={StaticResource ViewModel}, Path=EmployeesSource}"...
BUT the issue with my ViewModel is that under constructor I am passing several interfaces like following:
private readonly IEmployeeRepository _employeeRepository;
public EmployeeView_HolidaysViewModel(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
InitializeCollections();
InitializeCommands();
}
and therefore I can't make the above solution to work at all.
Any help with my problem would be highly appreciated. I simply need to get that list to that modal window's combobox.
In the end I managed to solve the problem by creating additional constructor to my class which looks like following:
public EmployeeView_HolidaysViewModel()
{
_employeeRepository = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IEmployeeRepository>();
InitializeCollections();
}
This way I can easily adopt Solution Nr 2 from the OP.

Accessing xaml elements from code if I have their string name

I have a code in wich i need to be able to access to a different amount of prebuilt grids in XAMl and make them visible or collapsed
All grid are named like grid1,grid2,grid3 etc. I have the ability in code to obtain the string name via a random number and get the name od the grid i'd like to show.
I searched online and people suggest to use the reflect method, but i'm having a hard time trying to figure out the syntax that i have to use.
Best regards
The most straight forward way of doing this is to just declare a Name value for each Grid...:
<Grid Name="Grid1">
...
</Grid>
... and then you can access them by that name from the code behind:
Grid1.Visibility = Visibility.Hidden;
However, this is WPF and that is generally not recommended. A preferred method would be to add some bool properties to your code behind or view model...:
public bool IsGrid1Visible { get; set; } // Implement INotifyPropertyChanged interface
... and then to bind these directly to the Grid1.Visibility property using a BooleanToVisibilityConverter:
<Grid Grid1.Visibility="{Binding IsGrid1Visible, Converter={StaticResource
BooleanToVisibilityConverter}}">
...
</Grid>
Then you can change the Grid.Visibility value by simply setting the IsGrid1Visible property to true or false.

Setting Label Text in XAML to string constant

I have a single string constant that I have to re-use in several different XAML layouts, so instead of duplicating it, I'd like to just bind it to a constant.
I have a class which defines the string in C#:
public static class StringConstants
{
public static string MyString { get { return "SomeConstant"; } }
}
I'd like to be able to set the value through XAML via something like the following:
<Label Content="{Binding local:StringConstants.MyString}"/>
Is this achievable? I've searched for examples, but I've only found samples that involve some tinkering in the code-behind and I'm wondering if there's a simpler, XAML-only solution if I know that I just need to set the value once based on a string value that will never change.
You are binding to a static member so you should use x:Static Markup Extension:
<Label Content="{Binding Source={x:Static local:StringConstants.MyString}}"/>
According to #H.B.'s comment it's not necessary to use Binding so it's simpler to use:
<Label Content="{x:Static local:StringConstants.MyString}"/>
Put the public static string MyString in your App.xaml.cs. Then you can reference it as follows.
Content="{Binding Source={x:Static Application.Current}, Path=MyString}"
In the case that you have a constant inside of a non-static class, this doesn't work.
My solution for binding to a constant inside of a view model (MVVM).
It uses a getter property with less code for wrapping.
// view model
public const string MyString = "abc";
public string MyStringConst => MyString;
.
<!-- WPF -->
<Label Content="{Binding MyStringConst, FallbackValue='abc'}" />
The FallbackValue is used for the Designer preview.

How to bind a static property in a different class to a GridViewColumn of WPF ListView?

Firstly I am using this for the ListView control itself:
ItemsSource="{Binding AllEffects}"
where 3 GridViewColumns already binded to to AllEffects.
But I have 2 more GridViewColumns that I want to bind to a separate static property found in:
public static class AllSupport
{
public static EffectSupportLookup<HardwareType, List<EffectSupport>>
}
public class EffectSupport
{
public bool IsSupported {get;set;}
}
I have tried this:
<GridViewColumn
Width="Auto"
Header="GPU">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox
Margin="0"
HorizontalAlignment="Center"
IsChecked="{Binding AllSupport, Mode=TwoWay}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
But at runtime, it complains that the there is no property called AllSupport on AllEffects. I don't want to store it inside AllEffects because this is a separate class already compatible with the UI, so I just want to bind it to:
AllSupport.EffectSupport[GPU].IsSupported
Any ideas?
Use x:Static Markup Extension.
Something like (never tested):
<Window xmlns:local="AssemblyName">
<ItemsControl
ItemsSource="{Binding Source={x:Static local:AllSupport.EffectSupport}}" />
</Window>
Or thru to a CollectionViewSource.
Update:
You have to specify internal path
Are you sure the local xmlns points to the right clr namespace (get assistance from the VS Xaml Intellisense)
Does your app compile before setting it in xaml?
Be more specific with your generic class implmenetation, what are you trying to achieve? is it a dictionary? a generic class? please reedit your code to give us the right look of your scenario.
I have 2 more GridViewColumns that I want to bind to a separate static property
I don't think you can do that, 1 ItemsSource means 1 SourceCollection.
But you can easily use LINQ to create a ad-hoc collection that includes those 2 columns
I don't want to store it inside AllEffects because this is a separate class already compatible with the UI,
If this means it's a ViewModel class, and if you want those columns in the View, then that is a very strong reason they should be stored in that class. Or in a separate, derived, ViewModel.

Categories