In my demo project I set up data for a listview via EntityFrameworkCore FromRawSql
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Things03.Module.Functions;
namespace Things03.Module.BusinessObjects
{
[NavigationItem("OldWayUseRibbonInstead")]
[DomainComponent]
public class ThingFilterHolder
{
public ThingFilterHolder()
{
ThingFilter = new ThingFilter();
ApplyFilter();
}
[Browsable(false)]
[Key]
public int Id { get; set; }
public ThingFilter ThingFilter { get; set; }
public virtual List<Thing> Things { get; set; }
public void ApplyFilter()
{
var search = StringFunctions.SafeString(ThingFilter.Search);
var sql = $"select Id, Name from Things where Name like '%{search}%'";
var db = DataFunctions.MakeDbContext();
Things = db.Things.FromSqlRaw(sql).ToList();
}
}
}
I find the technique is very powerful.
However the Navigation actions are disabled and Refresh does not work.
I expect that this is because there is no underlying ObjectSpace that "knows" the listview collection.
The Next and Previous do enable if I add a record, but only for the added record.
If I enable diagnostics I see the following about the RecordsNavigationController
<Controller Name="RecordsNavigationController" FullName="DevExpress.ExpressApp.SystemModule.RecordsNavigationController" Active="True">
<ActiveList>
<Item Key="View is assigned" Value="True" />
<Item Key="View type is ObjectView" Value="True" />
<Item Key="PropertyEditor has ObjectSpace" Value="True" />
</ActiveList>
<Actions>
<Action ID="PreviousObject" Caption="Previous Record" TypeName="SimpleAction" Category="RecordsNavigation" Active="True" Enabled="False" AdditionalInfo="">
<ActiveList>
<Item Key="Controller active" Value="True" />
<Item Key="ByContext_RequireSingleObject" Value="True" />
<Item Key="ListView or root DetailView" Value="True" />
<Item Key="Editor doesn't support focused row selection." Value="True" />
</ActiveList>
<EnabledList>
<Item Key="ByContext_RequireSingleObject" Value="True" />
<Item Key="Can move to previous" Value="False" />
</EnabledList>
</Action>
<Action ID="NextObject" Caption="Next Record" TypeName="SimpleAction" Category="RecordsNavigation" Active="True" Enabled="False" AdditionalInfo="">
<ActiveList>
<Item Key="Controller active" Value="True" />
<Item Key="ByContext_RequireSingleObject" Value="True" />
<Item Key="ListView or root DetailView" Value="True" />
<Item Key="Editor doesn't support focused row selection." Value="True" />
</ActiveList>
<EnabledList>
<Item Key="ByContext_RequireSingleObject" Value="True" />
<Item Key="Can move to next" Value="False" />
</EnabledList>
</Action>
</Actions>
</Controller>
The detail view is opened by an action on the toolbar.
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.ExpressApp.Win;
using System.Linq;
using Things03.Module.BusinessObjects;
namespace Things03.Module.Win.Controllers
{
public class ThingFilterController : ViewController
{
SimpleAction actThingScreen;
public ThingFilterController() : base()
{
TargetViewNesting = Nesting.Root;
actThingScreen = new SimpleAction(this, "Things", "View");
actThingScreen.Execute += actThingScreen_Execute;
}
private void actThingScreen_Execute(object sender, SimpleActionExecuteEventArgs e)
{
var holder = new ThingFilterHolder();
var holderType = holder.GetType();
var viewId = Application.FindDetailViewId(holderType);
if (SwitchToViewIfOpen(Application, viewId)) return;
var os = Application.CreateObjectSpace(typeof(Thing)); // any valid type would have done
var detailView = Application.CreateDetailView(os, holder);
e.ShowViewParameters.CreatedView = detailView;
e.ShowViewParameters.TargetWindow = TargetWindow.NewWindow;
e.ShowViewParameters.NewWindowTarget = NewWindowTarget.MdiChild;
}
private bool SwitchToViewIfOpen(XafApplication application, string viewId)
{
if (!(application.ShowViewStrategy is WinShowViewStrategyBase strategy)) return false;
foreach (var win in strategy.Windows.ToArray())
{
if (win.View == null) continue;
if (!win.View.Id.Equals(viewId)) continue;
win.Show();
return true;
}
return false;
}
}
}
The fix was to populate the list via ObjectSpace
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Things03.Module.Functions;
namespace Things03.Module.BusinessObjects
{
[NavigationItem("OldWayUseRibbonInstead")]
[DomainComponent]
public class ThingFilterHolder
{
public ThingFilterHolder()
{
ThingFilter = new ThingFilter();
ApplyFilter();
}
[Browsable(false)]
[Key]
public int Id { get; set; }
public ThingFilter ThingFilter { get; set; }
public virtual List<Thing> Things { get; set; }
[Browsable(false)] public IObjectSpace ObjectSpace { get; set; }
public void ApplyFilter()
{
var search = StringFunctions.SafeString(ThingFilter.Search);
var sql = $"select Id, Name from Things where Name like '%{search}%'";
var db = DataFunctions.MakeDbContext();
var things1 = db.Things.FromSqlRaw(sql).ToList();
Things = new List<Thing>();
if (ObjectSpace == null) return;
foreach (Thing t in things1)
{
Thing t2 = ObjectSpace.GetObject<Thing>(t);
Things.Add(t2);
}
}
}
}
I was able to achieve this by adding an ObjectSpace property to the ThingFilterHolder and having the Action set the property.
I also had to override the refresh controller.
public class MyRefreshController : RefreshController
{
public MyRefreshController() : base()
{
}
protected override void Refresh()
{
var holder = View.CurrentObject as ThingFilterHolder;
holder.ApplyFilter();
View.Refresh();
base.Refresh();
}
}
and the Delete
public class MyDeleteController : DeleteObjectsViewController
{
public MyDeleteController() : base()
{
}
protected override void Delete(SimpleActionExecuteEventArgs args)
{
base.Delete(args);
View.ObjectSpace.CommitChanges();
}
}