⚠️ This is an unmaintained hackathon project. Use at your own risk. It's open source, so fork the code and improve it!
Features | Descriptions |
---|---|
Supports all WPF controls | All properties are usable, classes are simply prefixed with Vx |
Supports third-party WPF controls! | The code analyzer auto-generates Vx wrappers for all UI controls, including controls from third-parties |
Supports your own WPF controls! | No need to do anything to your existing controls, just use them with the Vx prefix! |
Can be integrated into any existing project | Any VxComponent can be rendered to a standalone classic UIElement by simply calling ToUI() . |
Supports hot reload! | Everything within the Render() method supports hot reload! |
- Install the
Vx.Wpf
NuGet package to your WPF project - Create your first
VxComponent
public class MyComponent : VxComponent
{
protected override VxElement Render()
{
return new VxTextBlock
{
Text = "Hello world!"
};
}
}
- Display your
VxComponent
by callingToUI()
to transform it to a standard WPF UIElement!
public MainWindow()
{
InitializeComponent();
Content = new MyComponent().ToUI();
}
- Try using hot reload! You can enable hot reload on file save by opening the hot reload menu next to the "Start" button. Then, make changes within your
Render()
methods, click save, and watch the UI update!
Browse the samples folder to see more examples.
To make your components interactive, you have to add state. Each component's Render()
method is essentially a function on top of the component's current state. And if the component's state values change, Render()
will be called again and the UI will be updated with any changes that occured.
public class MyComponent : VxComponent
{
private readonly VxState<int> _count = new VxState<int>(0);
protected override VxElement Render()
{
return new VxStackPanel
{
Children =
{
new VxButton
{
Content = "Click me",
Click = b => _count.Value++
},
new VxTextBlock
{
Text = $"Times clicked: {_count.Value}"
}
}
};
}
}
You can define infinitely many VxState<T>
values.
The next thing to learn is how to handle text boxes (and other similar controls). This is a little different than classic WPF, since your component's state must also hold the current text value. Make sure to handle both the Text
and the TextChanged
properties as seen below.
public class MyComponent : VxComponent
{
private readonly VxState<string> _name = new VxState<string>("");
protected override VxElement Render()
{
return new VxStackPanel
{
Children =
{
new VxTextBlock
{
Text = $"Your name: {_name.Value}"
},
new VxTextBox
{
Text = _name.Value,
TextChanged = t => _name.Value = t.Text
}
}
};
}
}
This same pattern will follow for combo boxes, check boxes, and more.
public class MyComponent : VxComponent
{
private readonly VxState<string?> _drink = new VxState<string?>(null);
protected override VxElement Render()
{
return new VxStackPanel
{
Children =
{
new VxTextBlock
{
Text = $"Your drink: {_drink.Value}"
},
new VxComboBox
{
ItemsSource = new string[] { "Tea", "Coffee" },
SelectedValue = _drink.Value,
SelectionChanged = c => _drink.Value = c.SelectedValue as string
}
}
};
}
}
Event handlers are currently all projected as Action<T>
properties, where T
is the native UI type.
new VxTextBlock
{
Text = "Is mouse over: " + _isMouseOver.Value,
MouseEnter = (TextBlock t) => _isMouseOver.Value = true,
MouseLeave = (TextBlock t) => _isMouseOver.Value = false
}
⚠️ KNOWN ISSUE: The event args from the event are currently NOT projected. Most events, like TextChanged or SelectionChanged simply require access to the raw UI element to get the current value, so support for passing through those event args haven't been added yet.
Grids and many other UI elements rely on "attached properties" to set values on child elements. For example, Grid.SetColumn(textBlock, 1)
.
These attached properties can be used by calling an extension method on the VxElement
as seen below...
new VxBorder
{
Background = new SolidColorBrush(Colors.Red)
}.AttachedProperties(el => Grid.SetColumn(el, 1))
If you have to set multiple attached properties, you can add brackets to the handler as seen below...
new VxBorder
{
Background = new SolidColorBrush(Colors.Red)
}.AttachedProperties(el =>
{
Grid.SetColumn(el, 1);
Grid.SetColumnSpan(el, 2);
})
⚠️ KNOWN ISSUE: If attached properties are dynamically not set in a futureRender()
, they currently will not be removed from the element (they won't reset to their defaults). I recommend explicitly setting back to default value for now.
- Vx.Wpf uses C# source generators to generate, at runtime,
Vx*
wrappers for all of the WPF UI classes referenced in your project. This gives youVxStackPanel
,VxTextBlock
, and evenVxRandomThirdPartyControl
and more. All of the properties will be projected, including the event handlers projected as described above. - The
VxComponent
uses theseVx*
classes for the virtual UI tree. The initial call toRender()
returns the initial UI tree, and then subsequent calls toRender()
will perform a delta of the previous UI tree with the new one. The wrappedVx*
components are essential to ensure there's no additional initialization time of creating a newTextBlock
only to throw it away.
In VS, open the Vx.Wpf.sln, and then right click the Vx.Wpf
project and click "Pack". It'll output a new NuGet package in the bin folder.