WPF Components
Desktop query building controls with Material Design styling, MVVM architecture, and multi-framework targeting.
NetQueryBuilder.WPF targets .NET 6, .NET 8, and .NET 9 (Windows), so you can use it across all currently supported .NET versions.
Installation#
Install the WPF package via the .NET CLI. You will also need the core library and, optionally, the Entity Framework integration.
dotnet add package NetQueryBuilder.WPF
For Entity Framework Core support, add the EF integration package as well:
dotnet add package NetQueryBuilder dotnet add package NetQueryBuilder.EntityFramework dotnet add package NetQueryBuilder.WPF
Theme Resources#
NetQueryBuilder.WPF ships with a complete set of styles and templates in Generic.xaml.
You must merge this resource dictionary into your application or window resources before using any controls.
App-wide (recommended)
Add the resource dictionary to your App.xaml so all windows can use the controls:
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/NetQueryBuilder.WPF;component/Themes/Generic.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
Per-window
Alternatively, scope the theme to a single window:
<Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/NetQueryBuilder.WPF;component/Themes/Generic.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources>
Quick Start#
The fastest way to get a query builder on screen is to drop a QueryBuilderContainer into your XAML and assign a configurator in the code-behind.
XAML
<Window x:Class="MyApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpf="clr-namespace:NetQueryBuilder.WPF.Controls;assembly=NetQueryBuilder.WPF" Title="Query Builder" Height="600" Width="900"> <Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/NetQueryBuilder.WPF;component/Themes/Generic.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <ScrollViewer> <wpf:QueryBuilderContainer x:Name="QueryContainer"/> </ScrollViewer> </Window>
Code-behind
using NetQueryBuilder.Configurations; public partial class MainWindow : Window { private readonly AppDbContext _dbContext; public MainWindow() { InitializeComponent(); // Create DbContext (in-memory for demos) var options = new DbContextOptionsBuilder<AppDbContext>() .UseInMemoryDatabase("SampleDb") .UseLazyLoadingProxies() .Options; _dbContext = new AppDbContext(options); // Seed data and initialize InitializeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } private async Task InitializeAsync() { await _dbContext.SeedAsync(); // Create the EF Core configurator var configurator = new EfQueryConfigurator<AppDbContext>(_dbContext); // Assign to the container control QueryContainer.Configurator = configurator; } }
Component Hierarchy#
The WPF controls form a visual tree. The container manages the overall lifecycle while nested controls handle specific parts of the query.
QueryBuilderContainer ├── Entity Selector (ComboBox) ├── QueryBuilder<T> │ ├── SELECT Section (Property Checkboxes) │ ├── WHERE Section (ConditionEditor) │ ├── Expression Preview │ └── Execute Button └── QueryResultGrid (Paginated DataGrid)
QueryBuilderContainer
The top-level control that orchestrates the entire query building experience.
It provides an entity type selector (ComboBox) populated from the configurator,
manages IQuery instance lifecycle, and offers "New Query" functionality
to reset the builder.
public class QueryBuilderContainer : UserControl { /// <summary>The configurator that provides entity types and builds queries.</summary> public IQueryConfigurator? Configurator { get; set; } }
QueryBuilder<T>
The generic query builder control with three main sections. SELECT displays checkboxes for each available property so the user can choose which columns appear in results. WHERE hosts the hierarchical condition editor for filtering. An Expression Preview panel shows the generated LINQ expression in real time, and the Execute button runs the compiled query.
public class QueryBuilder<T> : UserControl { /// <summary>The query instance to build conditions for.</summary> public IQuery? Query { get; set; } }
ConditionEditor
A recursive router control that renders the correct editor based on the condition type.
It delegates to SimpleConditionEditor for leaf conditions (Field + Operator + Value)
and BlockConditionEditor for grouped conditions with AND/OR logic.
Blocks can be nested to arbitrary depth, enabling complex filter expressions.
QueryResultGrid
Displays paginated query results in a DataGrid with dynamically generated columns
matching the selected properties. Pagination controls (First, Previous, Next, Last) and a
total-items counter are built in.
MVVM Architecture#
Every control is backed by a dedicated ViewModel that inherits from ViewModelBase.
This base class implements INotifyPropertyChanged with a SetProperty
helper for clean property change notification. Commands use RelayCommand (synchronous)
and AsyncRelayCommand (asynchronous) with CommandManager.RequerySuggested
for automatic re-evaluation.
| ViewModel | Responsibility |
|---|---|
QueryBuilderContainerViewModel |
Entity selection, query creation, new-query command |
QueryBuilderViewModel |
Query configuration, execution, debounced expression updates (300ms) |
SimpleConditionViewModel |
Single condition management: property, operator, value |
BlockConditionViewModel |
Grouped conditions with AND/OR logic, child condition management |
QueryResultViewModel |
Pagination, result display, dynamic column generation |
ViewModelBase
All ViewModels inherit from ViewModelBase, which provides SetProperty for property changes and implements INotifyPropertyChanged:
public class MyViewModel : ViewModelBase { private string _name = ""; public string Name { get => _name; set => SetProperty(ref _name, value); } }
RelayCommand
Commands follow the ICommand pattern. RelayCommand handles synchronous
operations while AsyncRelayCommand tracks an _isExecuting flag with
try-finally to prevent double-execution.
// Synchronous command public ICommand ResetCommand => new RelayCommand(() => { // Reset logic }); // Async command with busy tracking public ICommand ExecuteCommand => new AsyncRelayCommand(async () => { await RunQueryAsync(); });
Debouncing
The QueryBuilderViewModel uses a DispatcherTimer with a 300ms
interval to debounce expression compilation. Every time a condition changes, the timer resets.
The expression only compiles after 300ms of inactivity, preventing performance issues
during rapid user input.
Styling & Theming#
The library ships with a professional Material Design-inspired color palette and a full set of control styles.
Color Scheme
| Role | Hex | Usage |
|---|---|---|
| Primary | #1976D2 |
Primary buttons, selected state, active accents |
| Secondary | #424242 |
Secondary buttons, text, borders |
| Success | #4CAF50 |
Execute button, positive actions |
| Error | #F44336 |
Remove/delete buttons, error states |
| Background | #FAFAFA |
Control backgrounds, panels |
Button Styles
Four named styles are provided for consistent button appearance across the query builder:
- PrimaryButtonStyle — Filled blue button for primary actions (Execute, Apply)
- SecondaryButtonStyle — Outlined button for secondary actions (Cancel, Reset)
- IconButtonStyle — Transparent button for toolbar icons (Add, Remove)
- PaginationButtonStyle — Pagination-specific styling for result navigation
Custom Overrides
Override default styles by redefining them after importing Generic.xaml. Use BasedOn to extend existing styles:
<ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/NetQueryBuilder.WPF;component/Themes/Generic.xaml"/> </ResourceDictionary.MergedDictionaries> <!-- Override the primary button color --> <Style x:Key="PrimaryButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}"> <Setter Property="Background" Value="#6200EA"/> <Setter Property="Foreground" Value="White"/> </Style> </ResourceDictionary>
Standalone Usage#
While QueryBuilderContainer is the simplest way to get started, you can use
the individual controls independently for more control over layout and behavior.
QueryBuilder without container
Use the QueryBuilder control directly when you manage entity selection yourself:
<wpf:QueryBuilder Query="{Binding MyQuery}"/>
var configurator = new QueryableQueryConfigurator<Person>(people.AsQueryable()); var query = configurator.BuildFor<Person>(); MyQuery = query;
QueryResultGrid independently
Display query results in a standalone paginated grid, useful when results come from a different source or you want to position the grid separately:
<wpf:QueryResultGrid Results="{Binding QueryResults}" DisplayProperties="{Binding SelectedProperties}"/>
Custom Operators#
Add custom operators through the core library's ConfigureConditions API.
The WPF controls automatically pick up any operators registered on the configurator
and display them in the operator dropdown.
configurator.ConfigureConditions(config => { config.AddOperator(new MyCustomOperator()); });
The custom operator will appear alongside the built-in operators (Equals, Contains, GreaterThan, etc.)
for any property type it supports. See the Core Library documentation
for details on implementing IOperator.
Framework Comparison#
Both the WPF and Blazor packages share the same core NetQueryBuilder engine, ensuring feature parity and consistent query behavior. The differences are in the UI layer:
| Feature | WPF | Blazor |
|---|---|---|
| UI Framework | WPF (XAML + Code-behind) | Blazor (Razor Components) |
| Architecture | MVVM with ViewModels | Component-based |
| Styling | ResourceDictionaries, Styles | CSS, Custom Components |
| Data Binding | DependencyProperty, TwoWay | Parameter binding, EventCallback |
| Rendering | Native WPF controls | HTML + CSS (MudBlazor) |