As all my my doubts are used to vanish here... :) I have got an other question.
I have a custom control in which I have a list of strings List and I'd like the user of my control to be able to edit the list in the properties editor but I doesn't work.. I can click on the button to make the Collection editor visible but the add key is not enabled and there's a message 'Property editing is not available'.
I made a custom quick and dirty class
public class DataUrl
{
public string Url {get; set;}
public DataUrl() { }
public override string ToString()
{
return Url.ToString();
}
}
and with this it works but its...
I suspect it doesn't work because string (or String) does not have a parameter-less constructor. I also tried to use the attribute
[NewItemTypesAttribute(typeof(string))]
but worthless..
Could someone help me ?
public class DataUrl : Component
{
private readonly List<string> _urlList = new List<string>();
public DataUrl() : base() {}
public DataUrl(IContainer container) : base()
{
container.Add(this);
InitializeComponent();
}
[Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<string> UrlList { get { return _urlList; } }
public override string ToString()
{
return Url.ToString();
}
}
Related
I'm attempting to create a graph extension to modify the behavior of one of the mobile scan screens, and I found in developer release notes for 2020 R1 the ability to use the [PXProtectedAccess] attribute to utilize protected members of a Graph from the extension, even though it does not directly inherit from it.
However, in order to utilize this, the Graph Extension class needs to be abstract and Acumatica no longer seems to recognize it when I do so. I'm sure that I am missing a crucial piece here but I can't figure out what it is based on documentation. EDIT: I was missing the [PXProtectedAccess] attribute on the class itself.
Now I am seeing something else when I try to actually call the abstract method. It throws Unable to cast object of type 'Wrapper.PX.Objects.IN.Cst_INScanIssueHost' to type 'INScanIssueHostDynamicInterface'. when I attempt to call any one of these protected members. I'm not sure what INScanIssueHostDynamicInterface refers to or how to resolve the type conflicts here.
Here is an excerpt of the code I'm using:
[PXProtectedAccess]
public abstract class INScanIssue_Extension : PXGraphExtension<INScanIssue, INScanIssueHost>
{
[PXProtectedAccess]
protected abstract void ReportError(string errorMsg, params object[] args);
public delegate void ProcessConfirmDelegate();
[PXOverride]
public virtual void ProcessConfirm(ProcessConfirmDelegate baseMethod)
{
ReportError("TEST");
}
}
I think you are on the right path. Your graphExtension should be abstract. Also please note that on your extension you use protected member of the graph extension by specifying the parameter of the attribute, as shown below:
public class MyGraph : PXGraph<MyGraph>
{
protected void Bar() { }
}
public class MyExt : PXGraphExtension<MyGraph>
{
protected void Foo() { }
}
[PXProtectedAccess]
public abstract class MySecondLevelExt : PXGraphExtension<MyExt, MyGraph>
{
[PXProtectedAccess]
protected abstract void Bar();
[PXProtectedAccess(typeof(MyExt))]
protected abstract void Foo();
}
So in your case, I think you can try to add that parameter to the ProctectedAccess attribute for those members that from INScanIssue(or overriden there ):
namespace PX.Objects.IN
{
[PXProtectedAccess]
public abstract class INScanIssue_Extension : PXGraphExtension<INScanIssue,
INScanIssueHost>
{
public static bool IsActive()
{
return true;
}
# region Protected Access
*[PXProtectedAccess(typeof(INScanIssue))]*
protected abstract void ClearHeaderInfo(bool redirect = false);
[PXProtectedAccess]
protected abstract void SetScanState(string state, string message = null, params object[] args);
[PXProtectedAccess(typeof(INScanIssue))]
protected abstract bool PromptLocationForEveryLine { get; }
........................................
Use the abstract extension only to access the protected members, then add a second level extension, that calls the exposed members from your first level extension. And I don't think you need to apply the attribute on the extension.
public abstract class INScanIssueProtectedAccessExt : PXGraphExtension<INScanIssue, INScanIssueHost>
{
[PXProtectedAccess]
public abstract void ReportError(string errorMsg, params object[] args);
}
public class INScanIssue_Extension : PXGraphExtension<INScanIssueProtectedAccessExt, INScanIssue, INScanIssueHost>
{
public delegate void ProcessConfirmDelegate();
[PXOverride]
public virtual void ProcessConfirm(ProcessConfirmDelegate baseMethod)
{
this.Base2.ReportError("TEST");
}
}
What I'm trying to do is create a site in Orchard that doesn't have a way for a user to register. An administrator will create the users.
What I have is module that defines the parts, records, views, etc. That is basically working.
Now what I'm trying to do is add a UserPart (from Orchard.Users) to one of the parts in my module.
I'm not sure how to do that. I need the fields displayed for the UserPart with the fields for the parent part in the same view. This also needs to be done in a way that when a save happens, all of the UserPart fields get sent to the Orchard.Users module.
Any suggestions, pointers or links on how to do that?
Thanks!
UPDATE...
The Activating Filter is an interesting idea. I initially chose the migration route. For now, I'll try and get that method working.
For simplicity, let's say I have a "Company" type (there's more to the actual type) that has a "CompanyName" and a UserPart.
Here's what the different pieces look like...
Migrations.cs (simplified)
public int Create()
{
SchemaBuilder.CreateTable("CompanyPartRecord", table => table.ContentPartRecord()
.Column("CompanyName", DbType.AnsiString, c => c.WithLength(50))
.Column("UserId", DbType.Int32));
SchemaBuilder.CreateForeignKey("FK_CompanyPartRecord_UserPartRecord", "CompanyPartRecord", new[] {"UserId" }, "Orchard.Users", "UserPartRecord", new[] { "Id" })
ContentDefinitionManager.AlterTypeDefinition("Company", type => type.WithPart("CommonPart").WithPart("UserPart"));
}
CompanyPartRecord
public class CompanyPartRecord : ContentPartRecord
{
public virtual string CompanyName { get; set; }
public virtual int? UserId { get; set; }
}
CompanyPart
public class CompanyPart : ContentPart<CompanyPartRecord>
{
internal LazyField<UserPart> UserPartField = new LazyField<UserPart>();
public string CompanyName
{
get { return Record.CompanyName; }
set { Record.CompanyName = value; }
}
public UserPart User
{
get { return UserPartField.Value;}
set { UserPartField.Value = value; }
}
}
Handler
public class CompanyPartHandler : ContentHandler
{
private readonly IContentManager _manager;
public CompanyPartHandler(IRepository<CompanyPartRecord> repository, IContentManager manager)
{
_manager = manager;
Filters.Add(StorageFilter.For(repository));
OnActivated<CompanyPart>(OnActivatedHandler);
}
private void OnActivatedHandler(ActivatedContentContext context, CompanyPart part)
{
if(part.User == null)
{
part.User = _manager.Create<UserPart>("User");
}
else
{
part.User = _manager.Get<UserPart>(part.User.Id);
}
}
}
Driver
public class CompanyPartDriver : ContentPartDriver<CompanyPart>
{
protected override DriverResult Editor(CompanyPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Company_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Company",
Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(CompanyPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Controller
public class AdminCompanyController : Controller, IUpdateModel
{
private readonly IOrchardServices _services;
private readonly INotifier _notifier;
private readonly IContentManager _contentManager;
private readonly ITransactionManager _transactionManager;
private readonly Localizer T = NullLocalizer.Instance;
public AdminCompanyController(IOrchardServices services)
{
_services = services;
_notifier = services.Notifier;
_contentManager = services.ContentManager;
_transactionManager = services.TransactionManager;
}
public ActionResult Create()
{
var company = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.BuildEditor(company);
return View(model);
}
[HttpPost, ActionName("Create")]
public ActionResult CreatePOST()
{
var contentItem = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid)
{
_transactionManager.Cancel();
return View(model);
}
_contentManager.Create(contentItem.ContentItem);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Edit(int Id)
{
var contentItem = _services.ContentManager.Get(Id);
dynamic model = _services.ContentManager.BuildEditor(contentItem);
return View(model);
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
var model = _contentManager.UpdateEditor(contentItem, this);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Delete(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
_contentManager.Destroy(contentItem.ContentItem);
return RedirectToAction("Index");
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties)
{
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
public void AddModelError(string key, LocalizedString errorMessage)
{
ModelState.AddModelError(key, errorMessage.ToString());
}
}
View (create)
#{ Layout.Title = T("Add Company").ToString(); }
#using (Html.BeginFormAntiForgeryPost())
{
#Display(Model)
}
Editor Template
#model SDS.Models.CompanyPart
<fieldset>
#Html.LabelFor(m => m.CompanyName)
#Html.TextBoxFor(m => m.CompanyName)
</fieldset>
#*
What goes here to display UserPart?
*#
So here's where I'm at. I can see the ContentItem (CompanyType). I can put in the name and save it. The name is getting saved to the db. Right now the UserPart is getting saved to the db, but all of the fields are blank.
The part I'm stuck on is what to put in the editor template to display the UserPart fields so that the values get to the UserPart driver and ultimately the db.
Any ideas on how to do that?
Thanks!
So you don't attach parts to parts, you attach parts to content items, and you can do that in multiple ways.
You can do it through the admin screen, but that isn't a code driven solution and would have problems if you have multiple environments or need to redeploy a fresh version of code.
You can attach the part when you create a new content item in the migration. This might be a good solution, if you already ran your migration you could possibly do it with an update migration. This allows the part to be managed through the admin screen, but has downsides because it can be removed and if you have code that relies on the part then you will start having errors.
The last way and best way is to attach the part dynamically using an Activating Filter.
ActivatingFilter class - Attaches a part to a content type from code. As opposed to attaching parts via migrations, parts attached using this filter will neither be displayed in the Dashboard, nor users will be able to remove them from types. It's a legitimate way of attaching parts that should always exist on a given content type.
So to do this:
1. Add a reference to Orchard.Users to your custom project.
2. Create a handler for you part. Such as MyPartHandler
3. Then add the activating handler like so
Filters.Add(ActivatingFilter.For<UserPart>("MyContentType"));
So now anywhere in your code you can access the UserPart if you already have your part, or the content item using
var userPart = myPart.As<UserPart>();
I have a few properties that I don't have a direct mapping in the database for, so I'm using the convention of having another variable that is mapped to the database, and a public variable that will be used to do all of my actual work. The common one is [mapping a boolean property to a char column][1], but I also have a StatusID property whose C# enum is different based on the derived type.
My public property has the [NotMapped] attribute on it, and my internal property has the [Column] attribute. I think there's something that because the public property isn't mapped, it's keeping the other property from being mapped as well.
In my project, I start with an abstract base Message class:
[Table("tblMessage")]
public abstract class Message {
[Column("msgIsSample")]
[Required]
internal string dbIsSample { get; set; }
[Column("msgStatusID")]
internal int? dbStatusId { get; set; }
[NotMapped]
public bool IsSample {
get {
return dbIsSample.ToUpper() == "Y";
}
set {
dbIsSample = value ? "Y" : "N";
}
}
public Message() {
this.IsSample = false;
this.dbStatusId = null;
}
}
Right now I only have a single class implementing the base class, Request:
public class Request : Message {
[NotMapped]
public int Status {
get {
return this.dbStatusId.HasValue ? this.dbStatusId.Value : 1;
}
set {
this.dbStatusId = value;
}
}
public Request()
: base() {
this.Status = 1;
}
}
Here is my context:
public class MyContext : DbContext {
public DbSet<Message> Messages { get; set; }
static MyContext() {
Database.SetInitializer<MyContext>(null);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Message>()
.Map<Request>(m => m.Requires("msgTypeID").HasValue(1));
}
}
Is this something that anyone else has run across? I haven't been able to find anything about why this isn't working, even though this looks like the accepted convention until the EF team adds additional custom mapping. Someone else has to have run across this issue.
When I try to execute this code, I get a DbUpdateException saying that it can't insert a NULL into column "msgIsSample" due to my having set that in the table creation script. This doesn't make any sense because the msgIsSample is defaulted to have a "N".
Instead of making it internal, make it protected internal.
At runtime, EF will subclass your entity dynamically. These extended classes are called dynamic proxies.
EF cannot set your property because it does not have access. To give EF access to your property, it must have either public or protected access. You can still have internal properties, but give subclasses access by adding the protected modifier.
[Table("tblMessage")]
public abstract class Message {
[Column("msgIsSample")]
[Required]
public string dbIsSample { get; protected internal set; }
[Column("msgStatusID")]
public int? dbStatusId { get; protected internal set; }
I have small WPF application. There are 5 projects in solution.
I want separate DOMAIN classes with UI ENTITIES and I want to use AUTOMAPPER.
You can download whole solution here: TestWPFAutomapper.zip
Domain class(Domain.Source.cs) with UI Entity(Entities.Destination.cs) have same signature.
In Entities.Destination.cs I would like to put other logic.
namespace DOMAIN
{
public class Source
{
public int Id { get; set; }
public int Position { get; set; }
}
}
using System.ComponentModel;
namespace ENITITIES
{
public class Destination : INotifyPropertyChanged
{
private int _id;
private int _position;
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public int Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged("Position");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
My data comes from DAL.DataContext using Entity Framework with CodeFirst. Here I´m using Source class.
using System.Data.Entity;
using DOMAIN;
namespace DAL
{
public class DataContext : DbContext
{
public DbSet<Source> Sources { get; set; }
}
}
Mapping is in BL.MyAppLogic.cs . In this class I have property Items which is ObservableCollection.
After puting another item into DB for Source class collection get refresh but for Destination is not refreshing.
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Linq;
using AutoMapper;
using DAL;
using DOMAIN;
using ENITITIES;
namespace BL
{
public class MyAppLogic
{
private readonly DataContext _dataContext = new DataContext();
public ObservableCollection<Source> Items { get; set; }
//public ObservableCollection<Destination> Items { get; set; }
public MyAppLogic()
{
Database.SetInitializer(new MyInitializer());
Mapping();
_dataContext.Sources.Load();
Items = _dataContext.Sources.Local;
//Items = Mapper.Map<ObservableCollection<Source>, ObservableCollection<Destination>>(_dataContext.Sources.Local);
}
private void Mapping()
{
Mapper.CreateMap<Source, Destination>().ReverseMap();
// I tried also Mapper.CreateMap<ObservableCollection<Source>, ObservableCollection<Destination>>().ReverseMap();
}
public int GetLastItem()
{
return _dataContext.Database.SqlQuery<int>("select Position from Sources").ToList().LastOrDefault();
}
public void AddNewItem(Destination newItem)
{
_dataContext.Sources.Add(Mapper.Map<Destination, Source>(newItem));
_dataContext.SaveChanges();
}
}
}
My problem is not with mapping, that’s works good, but with refreshing collection after adding or removing items from db. If I use DOMAIN.Source class everything works, collection is refreshing. But when I’m using ENTITIES.Destination data comes from DB and also I can put som new data to DB but refresing ObservableCollection is not working.
Please try to comment lines(14 & 23) in BL.MyAppLogic.cs and uncomment(15 & 24) and you’ll see what I mean.
Thank you for any help.
I got it but I don´t know if is correct.
Local has CollectionChanged event
so in constructor I put these lines
public MyAppLogic()
{
Database.SetInitializer(new MyInitializer());
Mapping();
_dataContext.Sources.Load();
_dataContext.Sources.Local.CollectionChanged += SourcesCollectionChanged;
Items = Mapper.Map<ObservableCollection<Source>, ObservableCollection<Destination>>(_dataContext.Sources.Local);
}
and handler looks
private void SourcesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var source = sender as ObservableCollection<Source>;
Mapper.Map(source, Items);
}
Now is my collection automating refreshing when I put something to DB in my UI.
Looks like automapper don´t put reference into Items, but create new instance.
I have two classes as below.
public class Destination
{
public Destination()
{
_StringCollection = new List<String>();
}
private ICollection<String> _StringCollection;
public IEnumerable<String> StringCollection
{
get
{
return _StringCollection.AsEnumerable<String>();
}
}
public void AddString(string str)
{
_StringCollection.Add(str);
}
}
public class Source
{
public List<String> StringCollection { get; set; }
}
I would like to map that for each member of source call AddString(member) on Destination.
I thought that maybe I could do something with a custom resolver but can't seem to figure out how.
No, you can't redirect to a specific method. You can expose as an ICollection, but that's it.