|
I have a behavior called SecurityBehavior that I'm using to control access to UI elements. I use it like this:
<TextBox Grid.Row="2"
Grid.Column="1"
Text="{Binding EmployeeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Height="32"
Width="250"
local:SecurityBehavior.SecurityTags="can_add_employees|can_edit_employees"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5"/>
I set two tags that allows the field to be enabled if the user can Add or Edit the employee name.
Here's my Behavior class:
public static class SecurityBehavior
{
#region Security Tags
public static string GetSecurityTags(DependencyObject obj)
{
return (string)obj.GetValue(SecurityTagsProperty);
}
public static void SetSecurityTags(DependencyObject obj, string value)
{
obj.SetValue(SecurityTagsProperty, value);
}
public static readonly DependencyProperty SecurityTagsProperty =
DependencyProperty.RegisterAttached("SecurityTags",
typeof(string), typeof(SecurityBehavior),
new PropertyMetadata("", OnSecurityTagsChanged));
private static void OnSecurityTagsChanged(object sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
bool canEnable = true;
var tags = e.NewValue.ToString().Split('|').ToList();
foreach (var tag in tags)
{
if (!string.IsNullOrEmpty(tag))
{
canEnable = canEnable & SecurityService.HasRights(tag);
}
}
element.IsEnabled = canEnable;
}
#endregion
}
Pretty straightforward. But now I ned one more thing. The UI's are always disabled until the "Edit" button on the toolbar is clicked. So, I added another DP like this:
<h1>region Allow Enable</h1>
public static bool GetAllowEnable(DependencyObject obj)
{
return (bool)obj.GetValue(AllowEnableProperty);
}
public static void SetAllowEnable(DependencyObject obj, bool value)
{
obj.SetValue(AllowEnableProperty, value);
}
public static readonly DependencyProperty AllowEnableProperty =
DependencyProperty.RegisterAttached("AllowEnable",
typeof(bool),
typeof(SecurityBehavior),
new PropertyMetadata(false, OnAllowEnableChanged));
private static void OnAllowEnableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
}
<h1>endregion
and use it like this:
<TextBox Grid.Row="2"
Grid.Column="1"
Text="{Binding EmployeeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Height="32"
Width="250"
local:SecurityBehavior.AllowEnable="{Binding AreFieldsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
local:SecurityBehavior.SecurityTags="can_add_employees|can_edit_employees"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5"/>
The value of the VM's AreFieldsEnabled property is now passed to the DP AllowEnable. So far so good.
So I now tried to modify the OnSecurityTagsChanged method:
private static void OnSecurityTagsChanged(object sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
var allowEnable = AllowEnable;
bool canEnable = true;
var tags = e.NewValue.ToString().Split('|').ToList();
foreach (var tag in tags)
{
if (!string.IsNullOrEmpty(tag))
{
canEnable = canEnable & SecurityService.HasRights(tag);
}
}
element.IsEnabled = canEnable;
}
I get a compilation error on the line with the commnent. How do I read the value of the other DP??
Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
It's an attached property, so you need to either call the attached property accessor, or call GetValue and pass in the dependency property.
Try something like this:
public static class SecurityBehavior
{
private static readonly char[] TagSeparators = { '|' };
public static readonly DependencyProperty SecurityTagsProperty =
DependencyProperty.RegisterAttached("SecurityTags",
typeof(string), typeof(SecurityBehavior),
new PropertyMetadata("", OnSecurityChanged));
public static string GetSecurityTags(DependencyObject obj)
{
return (string)obj.GetValue(SecurityTagsProperty);
}
public static void SetSecurityTags(DependencyObject obj, string value)
{
obj.SetValue(SecurityTagsProperty, value);
}
public static readonly DependencyProperty AllowEnableProperty =
DependencyProperty.RegisterAttached("AllowEnable",
typeof(bool),
typeof(SecurityBehavior),
new PropertyMetadata(false, OnSecurityChanged));
public static bool GetAllowEnable(DependencyObject obj)
{
return (bool)obj.GetValue(AllowEnableProperty);
}
public static void SetAllowEnable(DependencyObject obj, bool value)
{
obj.SetValue(AllowEnableProperty, value);
}
private static void OnSecurityChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
bool canEnable = GetAllowEnable(sender);
if (canEnable)
{
string[] tags = GetSecurityTags(sender).Split(TagSeparators, StringSplitOptions.RemoveEmptyEntries);
foreach (string tag in tags)
{
canEnable &= SecurityService.HasRights(tag);
}
}
var element = (UIElement)sender;
element.IsEnabled = canEnable;
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Ok, so I implemented your changes and the GetAllowEnable(sender) gets called fine. However, overall things still don't work right. Let me explain...
This is all in a small WPF test app. On the window I have a TextBox, and a ToggleButton called Enabled. When the Toggle is clicked, I set a property in the code behind called AreFieldsEnabled to True. This property is bound on the TextBox to the SecurityBehavior's AllowEnable DP:
<TextBox Grid.Row="2"
Grid.Column="1"
Text="{Binding EmployeeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Height="32"
Width="250"
local:SecurityBehavior.AllowEnable="{Binding AreFieldsEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
local:SecurityBehavior.SecurityTags="can_add_employees|can_edit_employees"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5"/>
The problem is that the SecurityTags DP gets called first, which calls GetAllowEnable, which return False by default because AllowEnable hasn't yet been set to True (using the ToggleButton). Later, when I toggle the button and set AreFieldsEnabled to True, the SecurityTags property is never called again, so nothing is enabled. Here's all the code:
Behavior
public static class SecurityBehavior
{
private static readonly char[] TagSeparators = { '|' };
#region Allow Enable
public static readonly DependencyProperty AllowEnableProperty =
DependencyProperty.RegisterAttached("AllowEnable",
typeof(bool),
typeof(SecurityBehavior),
new PropertyMetadata(false, OnAllowEnableChanged));
public static bool GetAllowEnable(DependencyObject obj)
{
return (bool)obj.GetValue(AllowEnableProperty);
}
public static void SetAllowEnable(DependencyObject obj, bool value)
{
obj.SetValue(AllowEnableProperty, value);
}
private static void OnAllowEnableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
}
#endregion
#region Security Tags
public static readonly DependencyProperty SecurityTagsProperty =
DependencyProperty.RegisterAttached("SecurityTags",
typeof(string), typeof(SecurityBehavior),
new PropertyMetadata("", OnSecurityChanged));
public static string GetSecurityTags(DependencyObject obj)
{
return (string)obj.GetValue(SecurityTagsProperty);
}
public static void SetSecurityTags(DependencyObject obj, string value)
{
obj.SetValue(SecurityTagsProperty, value);
}
private static void OnSecurityChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
bool canEnable = GetAllowEnable(sender);
if (canEnable)
{
string[] tags = GetSecurityTags(sender).Split(TagSeparators, StringSplitOptions.RemoveEmptyEntries);
foreach (string tag in tags)
{
canEnable &= SecurityService.HasRights(tag);
}
}
var element = (UIElement)sender;
element.IsEnabled = canEnable;
}
#endregion
}
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
If you look at my code again, both DPs call the same "property changed" method. Your AllowEnable property is calling an almost empty method instead.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Yup, I'm an idiot.
Thanks!
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Hi, I have a need to link two xaml windows due to a constraint on xml processing. Instead of sending one xml request, I have to send two.
I have some some research, and there is not a whole lot on doing this. I found an article that discussed:
How can i combine multiple XAML files using C# in WPF? - Stack Overflow[^]
Can anyone steer me in the right direction? I just want to run one xaml window then the next.
I put them into my project and nothing happened. I did not receive an error, but nothing happened.
|
|
|
|
|
If you're displaying the 2nd window from within the first one, just create an overloaded constructor in the 2nd window, and instantiate it with the necessary data.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
modified 21-Jun-20 7:40am.
|
|
|
|
|
I have a bit of a challenge here.
I'm working on an app that manages home building projects. A house can have floors, and this is a BuildingTypeEntity. Examples are "1 Story", "2 Story", etc. Parts, supplies, and equipment are delivered to the job site periodcally. Generally every few days. These deliveries are called "Schedule Packs", and that's represented by the SchedulePackEntity
These two entities are shown below:
public class BuildingTypeEntity : _EntityBase
{
private string _Caption;
public string Caption
{
get { return _Caption; }
set
{
SetProperty<string>("Caption", ref _Caption, value);
}
}
private int _NumberOfFloors;
public int NumberOfFloors
{
get { return _NumberOfFloors; }
set
{
SetProperty<int>("NumberOfFloors", ref _NumberOfFloors, value);
}
}
private ObservableCollection<SchedulePackEntity> _SchedulePacks;
public ObservableCollection<SchedulePackEntity> SchedulePacks
{
get { return _SchedulePacks; }
set
{
if (_SchedulePacks != value)
{
_SchedulePacks = value;
RaisePropertyChanged("SchedulePacks");
}
}
}
}
public class SchedulePackEntity : _EntityBase
{
private int _BuildingTypeId;
public int BuildingTypeId
{
get { return _BuildingTypeId; }
set
{
if (_BuildingTypeId != value)
{
_BuildingTypeId = value;
RaisePropertyChanged("BuildingTypeId");
}
}
}
private string _Caption;
public string Caption
{
get { return _Caption; }
set
{
if (_Caption != value)
{
_Caption = value;
RaisePropertyChanged("Caption");
}
}
}
private int _Sequence;
public int Sequence
{
get { return _Sequence; }
set
{
if (_Sequence != value)
{
_Sequence = value;
RaisePropertyChanged("Sequence");
}
}
}
private int _Floor;
public int Floor
{
get { return _Floor; }
set
{
if (_Floor != value)
{
_Floor = value;
RaisePropertyChanged("Floor");
}
}
}
private int _Days;
public int Days
{
get { return _Days; }
set
{
if (_Days != value)
{
_Days = value;
RaisePropertyChanged("Days");
}
}
}
}
The
Packs has a property called Days, which is the number of days past the project start date when the delivery will be made.
Ok, now the problem:
There is one part of the UI where the user can select which Packs they need for a project. Then, the pack data needs to become a "horizontal version" of the data represented as a DataGrid. Please see this screenshot.
On the left side is a list of Building Types built as Expanders with DataGrids inside. This part works fine.
The right side is what I need to build. Notice that there are expanders, and in each are one DataGrid for each floor. So on the right side, under 2 Story, are 1st Floor with its selected packs as columns of a grid, and next to that is 2nd Story with its selected packs as columns of a grid. There needs to be one grid for each floor, arranged horizontally. Only the packs that are checked on the left side should appear as columns in their respective DataGrids on the right side.
The summary of the problem is how to turn a vertical list of classes, 1 to however many there are in the list, to a DataGrid with those ojects as columns.
I'm open to suggestion on a different approach, or if someone has done this, I'd like to hear how.
Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I suspect you're going to need a CustomTypeDescriptor for the floor view-model.
CustomTypeDescriptor Class (System.ComponentModel) | Microsoft Docs[^]
Perhaps something like this:
public class BuildingTypeViewModel
{
public BuildingTypeViewModel(BuildingTypeEntity buildingType)
{
Caption = buildingType.Caption;
var floors = new List<BuildingFloorViewModel>(buildingType.NumberOfFloors);
for (int floorNumber = 1; floorNumber <= buildingType.NumberOfFloors; floorNumber++)
{
var floor = new BuildingFloorViewModel(buildingType, floorNumber);
if (!floor.Packs.SchedulePacks.IsEmpty) floors.Add(floor);
}
Floors = floors;
}
public string Caption { get; }
public IReadOnlyList<BuildingFloorViewModel> Floors { get; }
}
public class BuildingFloorViewModel
{
public BuildingFloorViewModel(BuildingTypeEntity buildingType, int floorNumber)
{
Floor = floorNumber;
Packs = new BuildingFloorPacksViewModel(buildingType, floorNumber);
}
public int Floor { get; }
public BuildingFloorPacksViewModel Packs { get; }
}
public class BuildingFloorPacksViewModel : CustomTypeDescriptor, System.Collections.IEnumerable
{
public BuildingFloorPacksViewModel(BuildingTypeEntity buildingType, int floorNumber)
{
SchedulePacks = buildingType.SchedulePacks.Where(p => p.Floor == floorNumber).ToDictionary(p => p.Caption);
var properties = SchedulePacks.Keys.Select(name => new SchedulePackPropertyDescriptor(name)).ToArray();
PropertyDescriptors = new PropertyDescriptorCollection(properties);
}
internal bool IsEmpty => SchedulePacks.Count == 0;
private IReadOnlyDictionary<string, SchedulePackEntity> SchedulePacks { get; }
private PropertyDescriptorCollection PropertyDescriptors { get; }
public override PropertyDescriptorCollection GetProperties() => PropertyDescriptors;
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) => PropertyDescriptors;
public System.Collections.IEnumerator GetEnumerator()
{
yield return this;
}
private sealed class SchedulePackPropertyDescriptor : PropertyDescriptor
{
public SchedulePackPropertyDescriptor(string name) : base(name, Array.Empty<Attribute>())
{
}
public override Type ComponentType => typeof(BuildingFloorPacksViewModel);
public override Type PropertyType => typeof(int);
public override bool IsReadOnly => false;
public override bool CanResetValue (object component) => false;
public override void ResetValue (object component) => throw new NotSupportedException();
public override bool ShouldSerializeValue (object component) => true;
public override object GetValue (object component)
{
var parent = (BuildingFloorPacksViewModel)component;
return parent.SchedulePacks[Name].Days;
}
public override void SetValue (object component, object value)
{
var parent = (BuildingFloorPacksViewModel)component;
parent.SchedulePacks[Name].Days = (int)value;
}
}
} Bind your right-hand view to a list of BuildingTypeViewModel objects. Within each expander, bind an ItemsControl to the Floors property. Within the data template for the ItemsControl , display the Floor , and bind the grid to the Packs property. Let WPF auto-generate the columns for the grid, and it should work.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Thanks Richard. I'll give it a try
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
ListBox is basically a list of ListBoxItem. How can you access this list in C#?
You can access parts of ListBoxItem threw SelectedItem but no the full item. For example you cannot get to background color of an item with SelectedItem.
So many years of programming I have forgotten more languages than I know.
|
|
|
|
|
|
This seems to be one way. It can get a ListBoxItem but not return it to the ListBox after it is edited.
To be more exact about what I want to do. Given a ListBox and search key, I want to highlight or change the color of items that contain the search key. I do not want to edit the text in the list. I want to keep the list browsable and still contain all items. Change the search key: a)highlight different items in the ListBox b)clearing those that are no longer contain the search key.
So many years of programming I have forgotten more languages than I know.
|
|
|
|
|
Try
Add a property to your listbox data source - IsMatched (probably a string with the colour name)
bind the back colour of your ListBoxItemTemplate to the IsMatched property of your data item
During the search event change the IsMatched value
Reset event should clear the IsMatched value back to the default, ptobably Transparent
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
Depends on your "templating". The "contents" are the things you load (.Items). The item template, default or custom, deals with the visuals. The default "data" is via ToString().
.net - Default ItemTemplate of Controls like ListBox/ListView - Stack Overflow
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
I'm working on an automatic updater for my WPF app. When I do a publish, it copies those files that changed to my server. Then, when the user double-clicks the icon, it checks my server for changes, downloads them, starts the app. Everything works great.
What I'd like to do now is set it up for a test environment. This means that I want it to target a copy of the production DB. It will copy the changed files to an alternate location, different than the production location, but target the Test DB instead of Production. All the code files will be the same.
The question is, what's the best way to tell the code to go look at the test DB versus the production db? One idea I was considering was using the existance of a file to trigger test mode. So, if file "TestMode.txt" was in the EXE's location, then start in test mode, else start in production mode.
Anyone have any better ideas?
Thanks
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
You are reinventing the wheel - clickonce deployment with different config files and different compile directives. You have 2 distinct deployments, UAT and production.
Alternatively if you just want to allow the user to change DBs then change the connection string when the user decides.
Caveat - change the background colour of the UAT version. You do not want the user to have any doubt which version they are entering their data into.
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
- Have you tried to publish using ClickInce in VS2017? Doesnt work. That's the whole reason I did my own.
- I'm hoping that the user should not have to take any action when launching the app, like picking a DB.
- Already did the coloring scheme for Test vs Production.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
1. Amazing, I used clickonce deployment for many years for both winforms and WPF and while fiddly to set up once done it worked like a dream for over 20 applications across 5 countries.
2. We deployed 2 distinct applications adding a UAT suffix to the assembly name and Assembly information. We then used Configuration Manager to have 2 additional formats pointing to the different locations and DBs.
3. [snicker] I once had to use a modal dialog to inform a user they were in the UAT version after they ignored the bright orange background.
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
I've been Googling this all morning, and all of the examples I've found use events in the code behind. I'm doing MVVM.
When I select a row in a data grid, I want to turn on cell editing on the 3rd cell. The first two cells are read only. By default, the user has to select the row, then double-clck the cell to enter edit mode.
Anyone have an example of how to do this?
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I suspect event handlers in the code-behind will be the only way to handle this.
MVVM discourages event handlers, but there are still times when you have to resort to them.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I guess I could wire up an event, then in the code behind's handler get the VM and call a method on it, but thsi seems ugly.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
I'm not sure why you'd need to call a method on the VM to start editing a cell in the grid?
The other alternative is to use a behaviour, as described in this SO answer:
c# - Single click edit in WPF DataGrid - Stack Overflow[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
That behavior is just what I'm looking for. I said use the code behind because all the examples I found on enabling cell editing use a row or cell selection event.
Thanks.
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Hello,
Whenever I'm trying to create a new window in a WPF project (C# or VB) in Visual Studio 2010, it fails to load the designer of the window and shows an error describing: Quote: Could not load type 'Microsoft.Expression.DesignModel.Core.ISharedInstanceBuilder' from assembly 'Microsoft.Expression.DesignModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.'
However, I've Visual Studio 2017 installed in my PC too. But it doesn't show this message and WPF projects open and run just fine in it. Even, my WinForms projects in Visual Studio 2010 run absolutely fine. The problem is with WPF projects only.
Why am I getting this error in Visual Studio 2010 and how do I solve this problem? Please help.
Regards,
Priyam
|
|
|
|
|