I have created a page which contains several controls, in this i have to get a image which is in the page. I have the image name as string value. I have made a for loop to find the image and return, but it is tedious while looping all the controls in the page if it is more and it is getting much time too.
// Passing the string and find as image
Image imgBack = FindControl<Image>((UIElement)Layout, typeof(Image), strSelectedimg);
// Function to find image
public T FindControl<T>(UIElement parent, Type targetType, string ControlName) where T : FrameworkElement
{
if (parent == null) return null;
if (parent.GetType() == targetType && ((T)parent).Name == ControlName)
{
return (T)parent;
}
T result = null;
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (FindControl<T>(child, targetType, ControlName) != null)
{
result = FindControl<T>(child, targetType, ControlName);
break;
}
}
return result;
}
Is there any other easy way to find the image in the page using the string value.?
If you use the Silverlight Toolkit, then you don't need this maintain this helper method yourself, because it comes with a similar one already as an extension method.
using System.Linq;
using System.Windows.Controls.Primitives;
// ...
private void DoStuff()
{
var myImage = this.MyRootLayoutPanel.GetVisualDescendants().OfType<Image>()
.Where(img => img.Name == "MyImageName").FirstOrDefault();
}
Alternatively, I don't know your exact scenario, but if you're crafting a properly templated Control rather than a simple UserControl or Page, you'd just do
public class MyFancyControl : Control
{
public MyFancyControl()
{
this.DefaultStyleKey = typeof(MyFancyControl);
}
// ...
public override void OnApplyTemplate()
{
var myImage = this.GetTemplateChild("MyImageName") as Image;
}
}
Perhaps you could build a lookup at the same time you're adding the images. If you post your code that adds the images at runtime, I could give you a more exact answer; but I'm thinking something like this:
private Dictionary<string, Image> _imageLookup;
private class ImageInfo
{
public string Name { get; set; }
public string Uri { get; set; }
}
private void AddImages(ImageInfo[] imageInfos)
{
this._imageLookup = new Dictionary<string, Image>();
foreach (var info in imageInfos)
{
var img = CreateImage(info.Name, info.Uri);
if (!this._imageLookup.ContainsKey(info.Name))
{
this._imageLookup.Add(info.Name, img);
}
AddImageToUI(img);
}
}
private Image CreateImage(string name, string uri)
{
// TODO: Implement
throw new NotImplementedException();
}
private void AddImageToUI(Image img)
{
// TODO: Implement
throw new NotImplementedException();
}
Then you could easily find the image later:
public Image FindControl(string strSelectedImg)
{
if (this._imageLookup.ContainsKey(strSelectedImg))
{
return this._imageLookup[strSelectedImg];
}
else
{
return null; // Or maybe throw exception
}
}
If you need to find more than just images, you could use a Dictionary<string, Control> or Dictionary<string, UIElement> instead.
Related
/// Serialization
/// Code 2012.05.23, [...] following Jani Giannoudis' examples
/// CodeProject Article "User Settings Applied",
/// http://www.codeproject.com/Articles/25829/User-Settings-Applied
/// </summary>
I'm using the above mentioned codeproject.com Code since a number of years successfully in different projects.
A few days ago, I converted one of those projects from .NET 4.x to .NET 6.0 and the unmodified code immediately stopped working (details below) for example in the following snippet:
// DataGridColumnSetting[] is based on System.Configuration.ApplicationSettingsBase
// https://learn.microsoft.com/en-us/dotnet/api/system.configuration.applicationsettingsbase?view=dotnet-plat-ext-6.0
private DataGridColumnSetting[] OriginalColumnSettings
{
get
{
return LoadValue(
Name,
typeof(DataGridColumnSetting[]),
SettingsSerializeAs.Binary,
null) as DataGridColumnSetting[];
}
}
Throwing a
System.NotSupportedException
HResult=0x80131515
Message=BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.
Source=System.Configuration.ConfigurationManager
StackTrace:
at System.Configuration.SettingsProperty..ctor(String name, Type propertyType, SettingsProvider provider, Boolean isReadOnly, Object defaultValue, SettingsSerializeAs serializeAs, SettingsAttributeDictionary attributes, Boolean throwOnErrorDeserializing, Boolean throwOnErrorSerializing)
at MyNamespace.Serialization.Setting.CreateSettingProperty(String name, Type type, SettingsSerializeAs serializeAs, Object defaultValue) in [...]MyNamespace\Serialization\Setting.cs:line 111
Since the very same code is working well in a .NET 4.8 project, I tried to find hints in the web and found
https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/5.0/binaryformatter-serialization-obsolete
(and a few others) also saying
Warning
"The BinaryFormatter type is dangerous and is not recommended for data processing. Applications should stop using BinaryFormatter as soon as possible, even if they believe the data they're processing to be trustworthy. BinaryFormatter is insecure and can't be made secure."
Actual Question:
Anyone else having the very same issue using the same code (from the above mentioned CodeProject Article "User Settings Applied").
(If not, I would start modifying (my personal flavor of) that code, and if successful post an answer to my question assuming others might hopefully benefit.)
Okay, apparently using JSON instead of Binary works as expected here in an http://www.codeproject.com/Articles/25829/User-Settings-Applied context (in .Net4.x as well as .Net 6).
The basic concept there is having a particular Serialization class for each UI Control one wants to handle. And only some of the article samples are using the deprecated SettingsSerializeAs.Binary, for example the one made for WPF DataGridcontrols. The concept modification that works for me is using (NuGet) Newtonsoft.Json for serialization there.
The typical pattern part where the article author is using SettingsSerializeAs as quoted in the question is now using SettingsSerializeAs.String instead of Binary:
private string OriginalColumnSettings
{
get
{
return LoadValue(
Name,
typeof(string),
SettingsSerializeAs.String,
defaultValue: null) as string;
}
}
Full real world (WPF) Sample for both .Net4.8 as well as .Net6:
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WhatEverNamespace.Serialization
{
/// <summary>
/// Serialization
/// Code 2012.05.23, [...] following Jani Giannoudis' examples
/// CodeProject Article "User Settings Applied",
/// http://www.codeproject.com/Articles/25829/User-Settings-Applied
/// </summary>
public class DataGridSetting : Setting
{
public static readonly DependencyProperty SettingProperty =
DependencyProperty.RegisterAttached(
"Setting",
typeof(string),
typeof(DataGridSetting),
new FrameworkPropertyMetadata(OnDataGridSettingChanged));
#region Fields
private static readonly Logger log = LogManager.GetCurrentClassLogger();
private readonly DataGrid dataGrid;
private readonly IList<DataGridColumn> dataGridColumns = new List<DataGridColumn>();
private bool isLoaded;
private readonly string name;
private bool useWidth = true;
private bool useDisplayIndex = true;
#endregion Fields
#region Constructors
public DataGridSetting(DataGrid dataGrid) :
this(dataGrid?.Name, dataGrid)
{
}
public DataGridSetting(string name, DataGrid dataGrid)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
this.name = name;
this.dataGrid = dataGrid ?? throw new ArgumentNullException(nameof(dataGrid));
}
#endregion Constructors
#region Properties
public string Name { get { return name; } }
public DataGrid DataGrid { get { return dataGrid; } }
public bool UseWidth
{
get { return useWidth; }
set { useWidth = value; }
}
public bool UseDisplayIndex
{
get { return useDisplayIndex; }
set { useDisplayIndex = value; }
}
public override bool HasChanged
{
get
{
var json = OriginalColumnSettings;
if (string.IsNullOrWhiteSpace(json))
return false;
var originalColumnSettings = JsonConvert.DeserializeObject<DataGridColumnSetting[]>(json);
DataGridColumnSetting[] columnSettings = ColumnSettings;
if (json.Length != columnSettings.Length)
return true;
for (int i = 0; i < originalColumnSettings.Length; i++)
{
if (!originalColumnSettings[i].Equals(columnSettings[i]))
return true;
}
return false;
}
}
private string OriginalColumnSettings
{
get
{
return LoadValue(
Name,
typeof(string),
SettingsSerializeAs.String,
defaultValue: null) as string;
}
}
private DataGridColumnSetting[] ColumnSettings
{
get
{
if (dataGrid?.Columns.Any() != true)
return null;
IList<DataGridColumnSetting> columnSettings =
new List<DataGridColumnSetting>(dataGrid.Columns.Count);
foreach (DataGridColumn dataGridColumn in dataGrid.Columns)
{
int index = dataGridColumns.IndexOf(dataGridColumn);
int displayIndex = dataGridColumn.DisplayIndex;
DataGridColumnSetting columnSetting = new DataGridColumnSetting
{
Index = index,
DisplayIndex = displayIndex,
Width = dataGridColumn.ActualWidth
};
columnSettings.Add(columnSetting);
}
return columnSettings.ToArray();
}
}
#endregion Properties
#region Methods
public static string GetSetting(DependencyObject dependencyObject)
{
return dependencyObject?.GetValue(SettingProperty) as string;
}
public static void SetSetting(DependencyObject dependencyObject, string settingKey)
{
dependencyObject?.SetValue(SettingProperty, settingKey);
}
public override void Load()
{
// Initialized event does not work since it's running too early in a WPF DataGrid
// ("dataGrid.Initialized += DataGridInitialized" in Jani's ListViewSettings.cs)
if (isLoaded == false)
SetupDataGridColumns();
try
{
DataGrid dataGrid = this.dataGrid;
if (dataGrid?.Columns.Any() != true)
return;
var json = OriginalColumnSettings;
var columnSettings = JsonConvert.DeserializeObject<DataGridColumnSetting[]>(json);
if (columnSettings?.Any() != true)
return;
for (int displayIndex = 0; displayIndex < columnSettings.Length; displayIndex++)
{
DataGridColumnSetting columnSetting = columnSettings[displayIndex];
if (columnSetting.Index < 0 || columnSetting.Index >= dataGridColumns.Count)
continue;
DataGridColumn dataGridColumn = dataGridColumns[columnSetting.Index];
if (useWidth)
dataGridColumn.Width = new DataGridLength(columnSetting.Width);
if (useDisplayIndex && columnSetting.Index != columnSetting.DisplayIndex)
dataGridColumn.DisplayIndex = columnSetting.DisplayIndex;
}
}
catch
{
if (ThrowOnErrorLoading)
throw;
}
}
public override void Save()
{
try
{
DataGridColumnSetting[] columnSettings = ColumnSettings;
if (columnSettings == null)
return;
var json = JsonConvert.SerializeObject(columnSettings);
SaveValue(
Name,
typeof(string),
SettingsSerializeAs.String,
json,
null);
}
catch
{
if (ThrowOnErrorSaving)
throw;
}
}
public override string ToString()
{
return string.Concat(name, " (DataGrid)");
}
private void SetupDataGridColumns()
{
dataGridColumns.Clear();
if (dataGrid == null)
return;
if (dataGrid.Columns.Count > 0)
isLoaded = true;
else
return;
for (int i = 0; i < dataGrid.Columns.Count; i++)
{
dataGridColumns.Add(dataGrid.Columns[i]);
}
}
private static void OnDataGridSettingChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (!(dependencyObject is DataGrid dataGrid))
{
log.Warn(CultureInfo.InvariantCulture,
"{0}.{1}(): invalid property attachment",
nameof(DataGridSetting),
nameof(OnDataGridSettingChanged));
return;
}
// search on the parent-tree for application settings
ApplicationSettings applicationSettings = FindParentSettings(dependencyObject);
if (applicationSettings == null)
{
log.Warn(CultureInfo.InvariantCulture,
"{0}.{1}(): missing application settings in parent hierarchy",
nameof(DataGridSetting),
nameof(OnDataGridSettingChanged));
return;
}
applicationSettings.Settings.Add(new DataGridSetting(dataGrid));
}
private static ApplicationSettings FindParentSettings(DependencyObject element)
{
while (element != null)
{
if (element.ReadLocalValue(
DependencyPropertySetting.ApplicationSettingsProperty) is ApplicationSettings applicationSettings)
return applicationSettings;
element = LogicalTreeHelper.GetParent(element);
}
return null;
}
#endregion Methods
}
}
Based on this page we've created a Wizard that has three steps. Everything works great, but we have one problem with the code given in the link, which is how it creates the next step instance (copy pasted from the link):
protected override IScreen DetermineNextItemToActivate(IList<IScreen> list, int lastIndex)
{
var theScreenThatJustClosed = list[lastIndex] as BaseViewModel;
var state = theScreenThatJustClosed.WorkflowState;
var nextScreenType = TransitionMap.GetNextScreenType(theScreenThatJustClosed);
var nextScreen = Activator.CreateInstance(nextScreenType, state);
return nextScreen as IScreen;
}
Currently, it looks like this in our project:
protected override IWizardScreen DetermineNextItemToActivate(IList<IWizardScreen> list, int lastIndex)
{
var theScreenThatJustClosed = list[lastIndex];
if (theScreenThatJustClosed == null) throw new Exception("Expecting a screen here!");
if (theScreenThatJustClosed.NextTransition == WizardTransition.Done)
{
TryClose(); // Close the entire Wizard
}
var state = theScreenThatJustClosed.WizardAggregateState;
var nextScreenType = _map.GetNextScreenType(theScreenThatJustClosed);
if (nextScreenType == null) return null;
// TODO: CreateInstance requires all constructors for each WizardStep, even if they aren't needed. This should be different!
var nextScreen = Activator.CreateInstance(nextScreenType, state, _applicationService, _wfdRegisterInstellingLookUp,
_adresService, _userService, _documentStore, _windowManager, _fileStore, _fileUploadService, _dialogService,
_eventAggregator, _aanstellingViewModelFactory);
return nextScreen as IWizardScreen;
}
As you can see, we have quite a few parameters we need in some steps. In step 1 we only need like two, but because of the Activator.CreateInstance(nextScreenType, state, ...); we still need to pass all of them.
What I'd like instead is to use a delegate Factory. We use them at more places in our project, and let AutoFac take care of the rest of the parameters. For each of the three steps we only need a delegate Factory that uses the state.
Because all three uses the same delegate Factory with just state, I've placed this Factory in their Base class:
public delegate WizardBaseViewModel<TViewModel> Factory(AggregateState state);
How I'd like to change the DetermineNextItemToActivate method:
protected override IWizardScreen DetermineNextItemToActivate(IList<IWizardScreen> list, int lastIndex)
{
var theScreenThatJustClosed = list[lastIndex];
if (theScreenThatJustClosed == null) throw new Exception("Expecting a screen here!");
if (theScreenThatJustClosed.NextTransition == WizardTransition.Done)
{
TryClose(); // Close the entire Wizard
}
return _map.GetNextScreenFactoryInstance(state);
}
But now I'm stuck at making the GetNextScreenFactoryInstance method:
public IWizardScreen GetNextScreenFactoryInstance(IWizardScreen screenThatClosed)
{
var state = screenThatClosed.WizardAggregateState;
// This is where I'm stuck. How do I get the instance using the Factory, when I only know the previous ViewModel
// ** Half-Pseudocode
var nextType = GetNextScreenType(screenThatClosed);
var viewModelFactory = get delegate factory based on type?;
var invokedInstance = viewModelFactory.Invoke(state);
// **
return invokedInstance as IWizardScreen;
}
Feel free to change the GetNextScreenFactoryInstance any way you'd like. As long as I can get the next Step-ViewModel based on the previous one in the map.
NOTE: Other relevant code, can be found in the link, but I'll post it here as well to keep it all together:
The WizardTransitionMap (only change is it not being a Singleton anymore, so we can instantiate a map outselves):
public class WizardTransitionMap : Dictionary<Type, Dictionary<WizardTransition, Type>>
{
public void Add<TIdentity, TResponse>(WizardTransition transition)
where TIdentity : IScreen
where TResponse : IScreen
{
if (!ContainsKey(typeof(TIdentity)))
{
Add(typeof(TIdentity), new Dictionary<WizardTransition, Type> { { transition, typeof(TResponse) } });
}
else
{
this[typeof(TIdentity)].Add(transition, typeof(TResponse));
}
}
public Type GetNextScreenType(IWizardScreen screenThatClosed)
{
var identity = screenThatClosed.GetType();
var transition = screenThatClosed.NextTransition;
if (!transition.HasValue) return null;
if (!ContainsKey(identity))
{
throw new InvalidOperationException(String.Format("There are no states transitions defined for state {0}", identity));
}
if (!this[identity].ContainsKey(transition.Value))
{
throw new InvalidOperationException(String.Format("There is no response setup for transition {0} from screen {1}", transition, identity));
}
return this[identity][transition.Value];
}
}
Our InitializeMap-method:
protected override void InitializeMap()
{
_map = new WizardTransitionMap();
_map.Add<ScreenOneViewModel, ScreenTwoViewModel>(WizardTransition.Next);
_map.Add<ScreenTwoViewModel, ScreenOneViewModel>(WizardTransition.Previous);
_map.Add<ScreenTwoViewModel, ScreenThreeViewModel>(WizardTransition.Next);
_map.Add<ScreenThreeViewModel, ScreenTwoViewModel>(WizardTransition.Previous);
_map.Add<ScreenThreeViewModel, ScreenThreeViewModel>(WizardTransition.Done);
}
We've changed the code:
The WizardTransitionMap now accepts Delegates. Also, instead of retrieving the type by the WizardTransition-enum value (Next, Previous, etc.), we now retrieve the Factory-invoke based on the next Type (so the inner Dictionary is reversed). So, this is our current WizardTransitionMap:
using System;
using System.Collections.Generic;
namespace NatWa.MidOffice.CustomControls.Wizard
{
public class WizardTransitionMap : Dictionary<Type, Dictionary<Type, Delegate>>
{
public void Add<TCurrentScreenType, TNextScreenType>(Delegate delegateFactory)
{
if (!ContainsKey(typeof(TCurrentScreenType)))
{
Add(typeof(TCurrentScreenType), new Dictionary<Type, Delegate> { { typeof(TNextScreenType), delegateFactory } });
}
else
{
this[typeof(TCurrentScreenType)].Add(typeof(TNextScreenType), delegateFactory);
}
}
public IWizardScreen GetNextScreen(IWizardScreen screenThatClosed)
{
var identity = screenThatClosed.GetType();
var state = screenThatClosed.State;
var transition = screenThatClosed.NextScreenType;
if (!ContainsKey(identity))
{
throw new InvalidOperationException(String.Format("There are no states transitions defined for state {0}", identity));
}
if (!this[identity].ContainsKey(transition))
{
throw new InvalidOperationException(String.Format("There is no response setup for transition {0} from screen {1}", transition, identity));
}
if (this[identity][transition] == null)
return null;
return (IWizardScreen)this[identity][transition].DynamicInvoke(state);
}
}
}
Our InitializeMap is now changed to this:
protected override void InitializeMap()
{
_map = new WizardTransitionMap();
_map.Add<ScreenOneViewModel, ScreenTwoViewModel>(_screenTwoFactory);
_map.Add<ScreenTwoViewModel, ScreenOneViewModel>(_screenOneFactory);
_map.Add<ScreenTwoViewModel, ScreenThreeViewModel>(_screenThreeFactory);
_map.Add<ScreenThreeViewModel, ScreenTwoViewModel>(_screenTwoFactory);
_map.Add<ScreenThreeViewModel, ScreenThreeViewModel>(null);
}
And our DetemineNexttemToActivate method to this:
protected override IWizardScreen DetermineNextItemToActivate(IList<IWizardScreen> list, int previousIndex)
{
var theScreenThatJustClosed = list[previousIndex];
if (theScreenThatJustClosed == null) throw new Exception("Expecting a screen here!");
var nextScreen = _map.GetNextScreen(theScreenThatJustClosed);
if (nextScreen == null)
{
TryClose();
return ActiveItem; // Can't return null here, because Caliburn's Conductor will automatically get into this method again with a retry
}
return nextScreen;
}
We also removed our entire WizardBaseViewModel and just let every Step-ViewModel implement the IWizardScreen:
public interface IWizardScreen : IScreen
{
AggregateState State { get; }
Type NextScreenType { get; }
void Next();
void Previous();
}
With the following implementation in our ScreenOneViewModel:
public AggregateState State { get { return _state; } }
public Type NextScreenType { get; private set; }
public void Next()
{
if (!IsValid()) return;
NextScreenType = typeof(ScreenTwoViewModel);
TryClose();
}
public void Previous()
{
throw new NotImplementedException(); // Isn't needed in first screen, because we have no previous
}
And the following implementation in our ScreenThreeViewModel:
public AggregateState State { get { return _state; } }
public Type NextScreenType { get; private set; }
public void Next()
{
NextScreenType = typeof(ScreenThreeViewModel); // Own type, because we have no next
TryClose();
}
public void Previous()
{
NextScreenType = typeof(ScreenTwoViewModel);
TryClose();
}
And each Step-ViewModel has its own delegate Factory, like this one for ScreenTwoViewModel:
public delegate ScreenTwoViewModel Factory(AggregateState state);
This is a basically a class library project which is somehow exposed as a WCF service. The code below is a part of the Data Access Layer. 'db' is an object of a DataContext class. To save a file, we do the following-
public static Guid SaveFile(FileDetails fileDetails)
{
System.Nullable<Guid> id = null;
SystemDataContext.UsingWrite(db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
});
return id ?? Guid.Empty;
}
Then, the below would execute-
public static void UsingWrite(Action<SoftCashCreditDBDataContext> action)
{
using (var context = new SystemDataContext())
{
try
{
action(context.Write);
}
catch (Exception ex)
{
DataAccessExceptionHandler.HandleExcetion(ex, Config.DataLayerPolicy);
}
}
}
public SystemDataContext()
{
if (_stack == null)
{
_stack = new Stack<SystemDataContext>();
this.Depth = 1;
this.Read = new SoftCashCreditDBDataContext(Config.ReadDatabaseConnection);
this.Write = new SoftCashCreditDBDataContext(Config.WriteDatabaseConnection);
}
else
{
var parent = _stack.Peek();
/// Increment level of node.
this.Depth = parent.Depth + 1;
/// Copy data context from the parent
this.Read = parent.Read;
this.Write = parent.Write;
}
_stack.Push(this);
}
public int Depth { get; private set; }
public bool IsRoot { get { return this.Depth == 1; } }
[ThreadStatic]
private static Stack<SystemDataContext> _stack = null;
public SoftCashCreditDBDataContext Read { get; private set; }
public SoftCashCreditDBDataContext Write { get; private set; }
#region IDisposable Members
public void Dispose()
{
var context = _stack.Pop();
if (context.IsRoot == true)
{
context.Read.Dispose();
context.Write.Dispose();
_stack = null;
}
}
#endregion
}
They have implemented LINQ to SQL here, and created a DBContext class. The 'SaveFileData()' method is actually part of that class, where it just calls an SP inside to save the file.
What I did not follow-
What exactly does the call to UsingWrite() do here? What is passed to the 'Action action' parameter, and what is it doing?
I understand your confusion. They use 2 delegates.
This is passed to the action parameter:
db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
}
So when UsingWrite is called, the SoftCashCreditDBDataContext delegate which was set in the Write delegate will call SaveFileData.
A simplified example to help you understand Action:
public void Main()
{
Test(x => Debug.Write(x));
}
private void Test(Action<string> testAction)
{
testAction("Bla");
}
This function will call Debug.Write with the argument x, which is a string that is passed to the test action function.
I have a class which has been steadily growing over time. It's called LayoutManager.
It started as a way for me to keep track of which dynamically created controls were on my page. So, for instance, I have this:
public CormantRadDockZone()
{
ID = String.Format("RadDockZone_{0}", Guid.NewGuid().ToString().Replace('-', 'a'));
MinHeight = Unit.Percentage(100);
BorderWidth = 0;
HighlightedCssClass = "zoneDropOk";
CssClass = "rightRoundedCorners";
LayoutManager.Instance.RegisteredDockZones.Add(this);
}
In this way, during the beginning stages of the Page Lifecycle, controls would be re-created and they would add themselves to their respective control's list.
A while later I found myself passing the 'Page' object between methods. This was for the sole purpose of being able to access controls found on Page. I thought to myself -- well, I already have a Layout Manager, I'll just treat the static controls in the same way.
As such, my Page_Init method now looks like this mess:
protected void Page_Init(object sender, EventArgs e)
{
SessionRepository.Instance.EnsureAuthorized();
LayoutManager.Instance.RegisteredPanes.Clear();
LayoutManager.Instance.RegisteredDocks.Clear();
LayoutManager.Instance.RegisteredDockZones.Clear();
LayoutManager.Instance.RegisteredSplitters.Clear();
LayoutManager.Instance.RegisteredSplitBars.Clear();
LayoutManager.Instance.RegisteredPageViews.Clear();
LayoutManager.Instance.CheckBox1 = CheckBox1;
LayoutManager.Instance.CheckBox4 = CheckBox4;
LayoutManager.Instance.StartEditButton = StartEditButton;
LayoutManager.Instance.FinishEditButton = FinishEditButton;
LayoutManager.Instance.RadNumericTextBox1 = RadNumericTextBox1;
LayoutManager.Instance.RadNumericTextBox2 = RadNumericTextBox2;
LayoutManager.Instance.LeftPane = LeftPane;
LayoutManager.Instance.DashboardUpdatePanel = DashboardUpdatePanel;
LayoutManager.Instance.CustomReportsContainer = CustomReportsContainer;
LayoutManager.Instance.HistoricalReportsContainer = HistoricalReportsContainer;
RegenerationManager.Instance.RegenerateReportMenu();
LayoutManager.Instance.MultiPage = DashboardMultiPage;
LayoutManager.Instance.MultiPageUpdatePanel = MultiPageUpdatePanel;
LayoutManager.Instance.TabStrip = DashboardTabStrip;
RegenerationManager.Instance.RegenerateTabs(DashboardTabStrip);
RegenerationManager.Instance.RegeneratePageViews();
LayoutManager.Instance.Timer = RefreshAndCycleTimer;
LayoutManager.Instance.Timer.TimerEvent += DashboardTabStrip.DoTimerCycleTick;
RegenerationManager.Instance.RegeneratePageState();
}
I'm looking at that and saying no, no, no. That is all wrong. Yet, there are controls on my page which are very dependent on each other, but do not have access to each other. This is what seems to make this so necessary.
I think a good example of this in practice would be using UpdatePanels. So, for instance, DashboardUpdatePanel is being given to the LayoutManager. There are controls on the page which, conditionally, should cause the entire contents of the dashboard to update.
Now, in my eyes, I believe I have two options:
Inside the object wanting to call UpdatePanel.Update(), I recurse up through parent objects, checking type and ID until I find the appropriate UpdatePanel.
I ask LayoutManager for the UpdatePanel.
Clearly the second one sounds cleaner in this scenario... but I find myself using that same logic in many instances. This has resulted in a manager class which looks like this:
public class LayoutManager
{
private static readonly ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly LayoutManager _instance = new LayoutManager();
private LayoutManager() { }
public static LayoutManager Instance
{
get { return _instance; }
}
private IList<CormantRadDock> _registeredDocks;
private IList<CormantRadDockZone> _registeredDockZones;
private IList<CormantRadPane> _registeredPanes;
private IList<CormantRadSplitter> _registeredSplitters;
private IList<CormantRadSplitBar> _registeredSplitBars;
private Dictionary<string, StyledUpdatePanel> _registeredUpdatePanels;
private IList<CormantRadPageView> _registeredPageViews;
public RadMultiPage MultiPage { get; set; }
public CormantTimer Timer { get; set; }
public CormantRadListBox HistoricalReportsContainer { get; set; }
public CormantRadListBox CustomReportsContainer { get; set; }
public StyledUpdatePanel MultiPageUpdatePanel { get; set; }
public CormantRadTabStrip TabStrip { get; set; }
public RadPane LeftPane { get; set; }
public StyledUpdatePanel DashboardUpdatePanel { get; set; }
public RadButton ToggleEditButton { get; set; }
public CheckBox CheckBox1 { get; set; }
public CheckBox CheckBox4 { get; set; }
public RadNumericTextBox RadNumericTextBox1 { get; set; }
public RadNumericTextBox RadNumericTextBox2 { get; set; }
public RadButton StartEditButton { get; set; }
public RadButton FinishEditButton { get; set; }
public IList<CormantRadDock> RegisteredDocks
{
get
{
if (Equals(_registeredDocks, null))
{
_registeredDocks = new List<CormantRadDock>();
}
return _registeredDocks;
}
}
public IList<CormantRadDockZone> RegisteredDockZones
{
get
{
if (Equals(_registeredDockZones, null))
{
_registeredDockZones = new List<CormantRadDockZone>();
}
return _registeredDockZones;
}
}
public IList<CormantRadPane> RegisteredPanes
{
get
{
if (Equals(_registeredPanes, null))
{
_registeredPanes = new List<CormantRadPane>();
}
return _registeredPanes;
}
}
public IList<CormantRadSplitter> RegisteredSplitters
{
get
{
if (Equals(_registeredSplitters, null))
{
_registeredSplitters = new List<CormantRadSplitter>();
}
return _registeredSplitters;
}
}
public IList<CormantRadSplitBar> RegisteredSplitBars
{
get
{
if (Equals(_registeredSplitBars, null))
{
_registeredSplitBars = new List<CormantRadSplitBar>();
}
return _registeredSplitBars;
}
}
public Dictionary<string, StyledUpdatePanel> RegisteredUpdatePanels
{
get
{
if( Equals( _registeredUpdatePanels, null))
{
_registeredUpdatePanels = new Dictionary<string, StyledUpdatePanel>();
}
return _registeredUpdatePanels;
}
}
public IList<CormantRadPageView> RegisteredPageViews
{
get
{
if (Equals(_registeredPageViews, null))
{
_registeredPageViews = new List<CormantRadPageView>();
}
return _registeredPageViews;
}
}
public StyledUpdatePanel GetBaseUpdatePanel()
{
string key = MultiPage.PageViews.Cast<CormantRadPageView>().Where(pageView => pageView.Selected).First().ID;
return RegisteredUpdatePanels[key];
}
public CormantRadDockZone GetDockZoneByID(string dockZoneID)
{
CormantRadDockZone dockZone = RegisteredDockZones.Where(registeredZone => dockZoneID.Contains(registeredZone.ID)).FirstOrDefault();
if (Equals(dockZone, null))
{
_logger.ErrorFormat("Did not find dockZone: {0}", dockZoneID);
}
else
{
_logger.DebugFormat("Found dockZone: {0}", dockZoneID);
}
return dockZone;
}
public CormantRadPane GetPaneByID(string paneID)
{
CormantRadPane pane = RegisteredPanes.Where(registeredZone => paneID.Contains(registeredZone.ID)).FirstOrDefault();
if (Equals(pane, null))
{
_logger.ErrorFormat("Did not find pane: {0}", paneID);
}
else
{
_logger.DebugFormat("Found pane: {0}", paneID);
}
return pane;
}
public CormantRadDock GetDockByID(string dockID)
{
CormantRadDock dock = RegisteredDocks.Where(registeredZone => dockID.Contains(registeredZone.ID)).FirstOrDefault();
if (Equals(dock, null))
{
_logger.ErrorFormat("Did not find dock: {0}", dockID);
}
else
{
_logger.DebugFormat("Found dock: {0}", dockID);
}
return dock;
}
}
Am I on a bad path? What steps are generally taken at this point?
EDIT1: I have decided to start down the path of improvement by finding the controls which are least-integrated into LayoutManager and finding ways of breaking them down into separate objects. So, for instance, instead of assigning the HistoricalReportsContainer and CustomReportsContainer objects to LayoutManager (which is then used in RegenerationManager.RegenerateReportMenu) I have moved the code to RadListBox "Load" event. There, I check the ID of the control which is loading and react accordingly. A strong first improvement, and has removed 2 controls and a method from LayoutManager!
Inversion of control is a general approach that people use for such problems. Your dependencies should not be stored in the one Jack-Bauer-kind-of-style class, but rather be injected, for example via constructor. Take a look at the IoC containers, such as Castle Windsor, Unity, NInject or any other.
I'm not sure how this would interact with future plans of MVC, but had you considered refactoring chunks of LayoutManager into an abstract class that inherits from Page, then having your actual pages inherit from that abstract class?
I have a datagridView, that is bound to a List. This List is made up of my class which contains 2 public properties, a String Name, and another List CustomList. See below:
public class MyClass2
{
public string Name
{ get; set;}
public string Description
{
get;
set;
}
}
public class MyClass
{
List<MyClass2> myList;
public string Name
{
get;
set;
}
public List<MyClass2> CustomList
{
get { return myList ?? (myList= new List<MyClass2>()); }
}
}
And then in my designer page:
List<MyClass> myClassList = new List<MyClass>();
dataGridView.DataSource = myClassList;
As it is right now, the only column that appears in the grid, is the MyClass:Name column, and the CustomList column does not show up. What I'd like is the CustomList column to show and to display something like "Collection" with the "..." button showing, and when it is clicked to have the "Collection Editor" to popup.
Does anyone know if this is possible and how to enable it? If there's a tutorial or anything that would help me out I'd appreciate that too. Thanks.
Using generics, I think, is a clean solution:
public class Sorter<T>: IComparer<T>
{
public string Propiedad { get; set; }
public Sorter(string propiedad)
{
this.Propiedad = propiedad;
}
public int Compare(T x, T y)
{
PropertyInfo property = x.GetType().GetProperty(this.Propiedad);
if (property == null)
throw new ApplicationException("El objeto no tiene la propiedad " + this.Propiedad);
return Comparer.DefaultInvariant.Compare(property.GetValue(x, null), property.GetValue(y, null));
}
}
Usage example:
string orderBy = "propertyName";
bool orderAsc = true;
List<MyExampleClass> myClassList = someMethod();
if (!string.IsNullOrEmpty(orderBy))
{
myClassList.Sort(new Sorter<MyExampleClass>(orderBy));
if (!orderAsc) myClassList.Reverse();
}
Short answer: Yes, you can do it with some code.
Long answer: To write the code is gonna be a pain in the ass, as you would have to know not only how the DataGridView behaves with custom columns, but you would need to know how to expose design time elements at runtime, which requires quite a bit of plumbing. Extensive knowledge about the PropertyGrid must also be known.
Note: This might a fun component to write. (I might actually tackle it if I get some time)
So using the 'button' approach posted by Dave, and some code that I found that implements the CollectionEditor, I can edit the CustomList in MyClass2
Here's my solution, although not quite as clean as I'd like:
Put this class somewhere:
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName)
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if (prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor)prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if (editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{ }
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
Add a button column to the data grid:
DataGridViewButtonColumn butt = new DataGridViewButtonColumn();
butt.HeaderText = "CustomList";
butt.Name = "CustomList";
butt.Text = "Edit CustomList...";
butt.UseColumnTextForButtonValue = true;
dataGridView.Columns.Add(butt);
dataGridView.CellClick += new DataGridViewCellEventHandler(dataGridView_CellClick);
Then call it in the button handler of the cell click.
if (e.RowIndex < 0 || e.ColumnIndex != dataGridView.Columns["CustomList"].Index)
return;
//get the name of this column
string name = (string)dataGridView[dataGridView.Columns["Name"].Index, e.RowIndex].Value;
var myClassObject= myClassList.Find(o => o.Name == name);
MyHelper.EditValue(this, myClassObject, "CustomList");
I'd still be interested in hearing other approaches, and not having to implement my own CollectionEditor. And I'm still interested in having it look more like what the TabControl uses to add TabPages in the PropertyGrid...by displaying the "..." button...but this might work for now.
What you want to do is add a column template with a button in it:
http://geekswithblogs.net/carmelhl/archive/2008/11/11/126942.aspx
In the handler for the button, get the selected MyClass item from the collection and bind its list property to a grid in your popup.